Thursday, April 11, 2013

Mock unit testing with Mockito


The main basic of unit testing is that an unit test must complete the prerequisite of the uniqueness and the complete isolation. However in some cases the test unit interacts directly with objects that are not instantiable nor easily controllable.

The Mocking is a response to this problem because by definition a Mock is a simulated object that reproduces the behavior of a real object in a controlled manner.

See in practice how to make Mocking with Mockito and at the same time make our tests more joyful. Note that the entire source code is available on Github.

Preamble

  • This article is not intended to describe or explain the different types of Mocks (mock / stub / dummy / fake). If you want to know more then I refer you to this page, which explains well the different terminologies and diversity of the Mocking world.
  • Code examples are purely illustrative and deliberately simplistic. In the real life depending on the test case it would have been preferable to use a new instance of an object rather than a Mock and even to make some tests builders to not falling into bad Mocking practices.

Business classes

To illustrate this article we use a simple business case based on a list of commands containing products.
The Product class represents a product with a price:
public class Product {

    private final BigDecimal price;

    public Product(BigDecimal price) {
        this.price = price != null ? price : BigDecimal.ZERO;
    }
 
    public BigDecimal getPrice() throws Exception {
        return price;
    }
}
Nothing complicated in this class excepted that the getPrice() method can throw an exception in theory at least.
The Order class represents a command with a list of products:
public class Order {

    List<Product> products;

    public Order(List<Product> products) {
        this.products = products != null ? products : new ArrayList<Product>();
    }
 
    public BigDecimal getTotalPrice() throws Exception {
        BigDecimal total = BigDecimal.ZERO;
        for (Product product : products) {
            total = total.add(product.getPrice());
        }
        return total;
    }
 
    public String formatTotalPrice(Locale locale) {
        try {
            return NumberFormat.getCurrencyInstance(locale).format(getTotalPrice());
        } catch (Exception e) {
            return StringUtils.EMPTY;
        }
    }
}
This class contains two methods:
  • getTotalPrice() : returns the order total price calculated from the product list.
  • formatTotalPrice() : returns the order total price formatted via a Locale.
The goal is to make unit tests using these two methods of course by using Mocks.

Creating Mocks

Mockito offers several methods to instantiate Mocks. The easiest way is to use the static ùethod mock() :
public class OrderTest {
  
 @Test
    public void should_instantiate_an_order_with_2_products() throws Exception {
        Product product1 = mock(Product.class);
        Product product2 = mock(Product.class);
        Order order = new Order(newArrayList(product1, product2));
        assertThat(product1).isNotNull();
        assertThat(product2).isNotNull();
        assertThat(order.products).hasSize(2);
    }
}
A second way a little more hype is to use the annotation @Mock :
@RunWith(MockitoJUnitRunner.class)
public class OrderTest {
    
    @Mock Product product1;
    @Mock Product product2;
    
    @Test
    public void should_instantiate_an_order_with_2_products() throws Exception {
        Order order = new Order(newArrayList(product1, product2));
        assertThat(product1).isNotNull();
        assertThat(product2).isNotNull();
        assertThat(order.products).hasSize(2);
    }
}
In this case it's necessary to use the runner MockitoJunitRunner which allows to instantiate automatically annotated Mocks. If you find this too intrusive then it is also possible to use the method initMocks() during the setup of the test :
public class OrderTest {

    @Mock Product product1;
    @Mock Product product2;

    @Before
    public void setUp() throws Exception {
        initMocks(this);
    }

    @Test
    public void should_instantiate_an_order_with_2_products() throws Exception {
        Order order = new Order(newArrayList(product1, product2));
        assertThat(product1).isNotNull();
        assertThat(product2).isNotNull();
        assertThat(order.products).hasSize(2);
    }
}
However I would not recommend abusing of these annotations because by experience tests tend to increase rapidly in complexity due to the number of treatments in the setup phase. This often occurs when you need to use several testing frameworks at the same time such as Spring or Arquillian Test. Then we have doubly or triply annotated attributes which makes testing more complicated to refactor.

Simulate behavior

In addition to the creation modes we can also define the default behavior of our Mock; i.e values ​​returned when a call to a method or an access to an attribute is make :
  • RETURNS_DEFAULTS : returns an appropirate value for primitive wrapping classes (Integer, Boolean, ...), collections, and toString() and compareTo() methods else null. Currently, this is the default mode in version 1.9.5;
  • RETURNS_SMART_NULLS : this is the same mode as before except that exception messages are more explicit instead of returning null the object ReturnsSmartNull will be returned. Become, in version 2.0, the default mode;
  • RETURNS_MOCKS : this is a useful mode because instead of returning null for previously untrated cases Mockito will try to create a Mock;
  • RETURNS_DEEP_STUBS : it is a very useful mode also to use with cautious as it will help manage long chains of calls that are often found in the legacy code.
In practice this is what these behaviors gives :
 
    @Test
    public void test_default_answer_mocking() throws Exception {
        // RETURNS_DEFAULTS
        Product product1 = mock(Product.class, RETURNS_DEFAULTS);
        assertThat(product1.getPrice()).isNull();
        // product1.getPrice().abs() -> NPE with an exception message not explicit
    
        // RETURNS_SMART_NULLS
        Product product2 = mock(Product.class, RETURNS_SMART_NULLS);
        assertThat(product2.getPrice()).isNotNull();
        // product2.getPrice().abs() -> NPE with an explicit exception message
    
        // RETURNS_MOCKS
        Product product3 = mock(Product.class, RETURNS_MOCKS);
        assertThat(product3.getPrice()).isNotNull();
        assertThat(product3.getPrice().abs()).isNotNull();
        assertThat(product3.getPrice().getClass().getName()).contains(BigDecimal.class.getName());
    }
But we must keep in mind an anecdotal quote but true :
every time a mock returns a mock a fairy dies
Now we know correctly how to create our Mocks we will simulate the behavior of a product that returns a specific price and then test our method getTotalPrice() :
    
    @Test
    public void should_have_a_total_price_equal_to_8_99() throws Exception {
        Product product1 = mock(Product.class);
        Product product2 = mock(Product.class);
        when(product1.getPrice()).thenReturn(new BigDecimal("3.99"));
        when(product2.getPrice()).thenReturn(new BigDecimal("5.00"));
        Order order = new Order(newArrayList(product1, product2));
        assertThat(order.getTotalPrice()).isEqualTo(new BigDecimal("8.99"));
    }
We may also want to trigger an exception :
    
    @Test(expected = IllegalStateException.class)
    public void should_throws_exception_when_calling_get_price() throws Exception {
        Product product1 = mock(Product.class);
        when(product1.getPrice()).thenThrow(new IllegalStateException());
        product1.getPrice();
    }
Sometimes when the test case present complex methods such as thenReturn() and thenThrow() are not be enough we may have to define its own method of response using thenAnswer(). A classic case is when you need to change the behavior of a method based on these arguments which are not predictable when writing the test.

Using partial Mocks (Spy)

Now we're going to do another unit test method formatTotalPrice(). This method you will have noticed uses getTotalPrice() previously tested.
This is a first version :
    
    @Test
    public void should_format_total_price_to_10_00_euros_string() throws Exception {
        Product product = mock(Product.class);
        when(product.getPrice()).thenReturn(BigDecimal.TEN);
        Order order = new Order(newArrayList(product));
        assertThat(order.formatTotalPrice(Locale.FRANCE)).isEqualTo("10,00 €");
    }
As a reminder when you do a unit test the unit may be a method, a class, or a package. In our case if we consider the unit as a method then the previous example is not completely right. Why? Simply because the system is not tested in complete isolation because it depends on the method getTotalPrice() that under no circumstances was simulated. This is where the notion of partial Mock or commonly known as Spy in Mockito take part. A Spy can be seen as a copy of an instance of a class.
And here's the same test but with the method unicity this time :
    
    @Test
    public void should_format_total_price_to_10_00_euros_string() throws Exception {
        Order order = spy(new Order(mock(List.class));
        doReturn(BigDecimal.TEN).when(order).getTotalPrice();
        assertThat(order.formatTotalPrice(Locale.FRANCE)).isEqualTo("10,00 €");
    }
When using a Spy it is advisable to use the form doReturn() | doThrow() | doAnswer() according to the Mockito documentation.

Verify the Mock interaction

Simulate some behaviors are good but check the interaction can be in some cases even better.
For example we want to ensure that in our previous test method, formatTotalPrice() makes a call to the method getTotalPrice() one and only once :
    
    @Test
    public void should_format_total_price_to_10_00_euros_string() throws Exception {
        Order order = spy(new Order(mock(List.class));
        doReturn(BigDecimal.TEN).when(order).getTotalPrice();
        assertThat(order.formatTotalPrice(Locale.FRANCE)).isEqualTo("10,00 €");
        verify(order, times(1)).getTotalPrice();
    }
The method verify() of Mockit, permit to verify that a behavior occurred :
  • Exactly n-times : times(n)
  • At least n-times : atLeastOnce(n)
  • Never : never()
By experience testing the behavior of a Mock is often used when we wants to ensure that a method is never called when certain conditions are not met, or as in the previous example, to ensure that it does not call several times a method that is costly in time or CPU. Typically a call to a web service, a database loading, or an IO system. It can also be used simply to verify that a treatment has been performed.

And BDD, is that possible?

For lovers of BDD (Behavior Driven Development) since 1.8 version Mockito offers the class BDDMockito.
Below previous tests rewritten in BDD style :
@RunWith(MockitoJUnitRunner.class)
public class OrderBDDTest {

    @Mock Product product1;
    @Mock Product product2;
 
    @Test
    public void should_have_a_total_price_equal_to_8_99() throws Exception {
        Order order = new Order(newArrayList(product1, product2));
        // given
        given(product1.getPrice()).willReturn(new BigDecimal("3.99"));
        given(product2.getPrice()).willReturn(new BigDecimal("5.00"));
        // when
        BigDecimal totalPrice = order.getTotalPrice();
        // then
        assertThat(totalPrice).isEqualTo(new BigDecimal("8.99"));
    }

    @Test
    public void should_format_total_price_to_10_00_euros_string() throws Exception {
        Order order = spy(new Order(null));
        // given
        given(order.getTotalPrice()).willReturn(BigDecimal.TEN);
        // when
        String price = order.formatTotalPrice(Locale.FRANCE);
        // then
        assertThat(price).isEqualTo("10,00 €");
    }
}

Conclusion

Every good craftsman must use a mock library for one hand make them faster and concise to write and secondly to promote the test development driven (TDD).
Mockito is a reference library by its power and its easy API to use however Mockito also has its own limitations because static and final methods/attributes are still not well managed. If in your project you have good old legacy code with static then you can take a look at the PowerMock library.
If you're still not convinced then here is a link to some relevant quotes.

No comments:

Post a Comment