Advanced mocking with Mockito

Tomasz Głuszak

Purpose of unit tests is to test small chunks of code independently, in separation from any dependencies. In many cases to keep this kind of separation tools like Mockito come in handy. Mockito is mocking framework, Java library that allow to simulate calls on dependent objects instead of calling the real ones. A mock object returns a dummy data corresponding to dummy input passed to it.

Simple case

Let’s say we have simplified service called CustomerService with addCustomer() operation that accept Customer entity as parameter. It uses CustomerDao which connects to database. While testing CustomerService we don’t want to use real database connection so we will mock CustomerDao.

public class CustomerService {
    private CustomerDao customerDao;

    public boolean addCustomer(Customer customer) {
        if (customerDao.getById(customer.getId()) != null) {
            return false;
        }
        return customerDao.save(customer);
    }
}
JUnit test with where customer already exists will look like this:

@RunWith(MockitoJUnitRunner.class)
public class CustomerServiceTest {
    @InjectMocks
    private CustomerService underTest;
    @Mock
    private CustomerDao customerDao;
    private long customerId = 28;
    private Customer customer = new Customer(customerId);

    @Test
    public void addCustomerWhenExists() {
        when(customerDao.getById(customerId)).thenReturn(customer);
        boolean result = underTest.addCustomer(customer);
        assertFalse(result);
        verify(customerDao, never()).save(customer);
    }
}

when(…).thenReturn(…) means, that when method getById() is called with parameter customerId=28 then object customer will be returned simulating scenario where customer with given id already exists in database.

In the last line we verify that method save was never called.

Mock method arguments verification

In some cases we need to check more information about arguments that are passed to methods we are mocking, especially when arguments are passed between mocks or are modified during execution.

Let’s extend our example by adding CustomerNumberGenerator that will generate some business-related customer number and set it to customer object before saving the entity.

public class CustomerService {
    private CustomerDao customerDao;
    private CustomerNumberGenerator customerNumberGenerator;

    public boolean addCustomer(Customer customer) {
        if (customerDao.getById(customer.getId()) != null) {
            return false;
        }
        customerNumberGenerator.generate(customer);
        return customerDao.save(customer);
    }
}
@FunctionalInterface
public interface CustomerNumberGenerator {
    void generate(Customer customer);
}
In next unit test we want to allow customerNumberGenerator to call method generate() on implementation that we provide in test and check the argument passed to customerDao.save().
@Captor
private ArgumentCaptor<Customer> customerCaptor;
private String customerNumber = "STC3288";
@Test
public void addCustomerCheckCustomerNumber() {
    underTest.setCustomerNumberGenerator(customer -> customer.setCustomerNumber(customerNumber));
    when(customerDao.getById(customerId)).thenReturn(null);
    when(customerDao.save(customer)).thenReturn(true);
    boolean result = underTest.addCustomer(customer);
    assertTrue(result);
    verify(customerDao).save(customerCaptor.capture());
    assertEquals(customerNumber, customerCaptor.getValue().getCustomerNumber());
}

We use ArgumentCaptor<Customer> object to capture argument passed to mock.
In the test, at first we provide our test implementation of CustomerNumberGenerator to CustomerService using lambda expression.
When calling verify() on customerDao we use capture()  method on captor object so in the following line when getValue() is called we have access to object passed to mocked method as argument. There we check if customerNumber property is set properly.

Using Answers for complex mocks

A method may perform more complex behavior than merely adding or setting value. For these situations we can use Mockito’s Answer to add the behavior we need. Instead of setting custom CustomerNumberGenerator in test we will use mockito doAnswer().

@Mock
private CustomerNumberGenerator customerNumberGenerator;
@Test
public void addCustomerWithCustomerNumber() {
    when(customerDao.getById(customerId)).thenReturn(null);
    when(customerDao.save(customer)).thenReturn(true);
    doAnswer(invocation -> {
        Customer customer = invocation.getArgument(0);
        customer.setCustomerNumber(customerNumber);
        return null;
    }).when(customerNumberGenerator).generate(customer);
    boolean result = underTest.addCustomer(customer);
    assertTrue(result);
    verify(customerDao).save(customerCaptor.capture());
    assertEquals(customerNumber, customerCaptor.getValue().getCustomerNumber());
}

In this example we use doAnswer(…).when() because generate() is void method on it is not accepted by when(…).thenAnswer(…).
Inside lambda expression we set customerNumber to the customer entity as in previous example but in more compound cases it can be used to do more complex operations, even perform argument checks.
Then we have verifications and assertions as in previous example.

Verifying real method calls

In some case we would like to invoke real methods and verify that invocation. In that case Mocktio comes with @Spy annotation.

In this example we assume there is some implementation of CustomerNumberGenerator and we want to the real method generate() and verify it.

@Spy
private CustomerNumberGeneratorImpl customerNumberGenerator;
@Test
public void addCustomerRealGenerator() {
    when(customerDao.getById(customerId)).thenReturn(null);
    when(customerDao.save(customer)).thenReturn(true);
    boolean result = underTest.addCustomer(customer);
    assertTrue(result);
    verify(customerDao).save(customer);
    verify(customerNumberGenerator).generate(customer);
}

Using @Spy annotation CustomerNumberGeneratorImpl is automatically injected in underTest object (just like with @Mock annotation), the real method generate() is called and we are able to verify it in the last line of code.

Conclusion

Mockito is easy to use yet powerful tool that simplifies unit testing, especially when it comes to dependency separation. It works well in trivial mocking cases like “return something when some method is called”, but it reveals its full potential in more complex scenarios such as verifying arguments passed to method, void methods handling or real method call verification.

Poznaj mageek of j‑labs i daj się zadziwić, jak może wyglądać praca z j‑People!

Skontaktuj się z nami