Tuesday, June 3, 2014

Mockito Captors and verify(): How to check objects created and verify method calls within a method

Like any good developer I'm sure you write unit tests for each line of your code. Now, you could simply write tests that will execute each line of your code and leave it at that and assume that if the line of code executed then it must be correct but what if you want to actually check that a specific method was called or that a specific method was NOT called because of your setup? What if you want to check the attributes of an object created within the method your testing? Well, with a couple quick calls through Mockito you can easily verify that a method was called and even get objects passed into these method calls.

Example

To keep it simple lets say you have a DAO class that has three methods, a get, an insert, and an update for your domain object.
public interface Dao {
    Domain getDomain(int id);
    void insertDomain(Domain domain);
    void updateDomain(Domain domain);
}
You also have a service class that takes in a web form, translates it to a domain object and inserts or updates the object depending on if the object is found in the database.
public class Service {
    private Dao dao;

    public void processWebForm(WebForm webForm) {
        Domain domain = dao.getDomain(webForm.getId());
        if (domain != null) {
            domain.setField1(webForm.getField1());
            dao.updateDomain(domain);
        } else {
            domain = new Domain();
            domain.setId(webForm.getId());
            domain.setField1(webForm.getField1());
            dao.insertDomain(domain);
        }
    }

    public Dao getDao() {
        return dao;
    }

    public void setDao(Dao dao) {
        this.dao = dao;
    }
}
One quick note, I'm not showing the domain or webform objects here but you can click here to get all the source code for this example. Also, I realize this isn't the most perfectly written code with all the best programming practices but for the purpose of this example it's sufficient.
Now, for testing I could simply call the processWebForm method to ensure that it doesn't fail. I could even call it twice with two different WebForm objects where one should be inserted and one should be updated but what if I want to make sure each of the calls do what I expect? Enter Mockito. The following code snippet will test that the submitted WebForm will result in a Domain object being created and inserted into the database.
@RunWith(MockitoJUnitRunner.class)
public class ServiceTest {
    @Test
    public void testInsertWebForm() throws Exception {
        Dao dao = Mockito.mock(Dao.class);
        Domain d = new Domain();
        d.setId(1);
        d.setField1("field1");
        Mockito.when(dao.getDomain(Matchers.eq(1))).thenReturn(d);
        Service s = new Service();
        s.setDao(dao);
        WebForm wf = new WebForm();
        wf.setField1("testValue");
        s.processWebForm(wf);
        Mockito.verify(dao, Mockito.never()).updateDomain(Matchers.anyObject());
        ArgumentCaptor domainCaptor = ArgumentCaptor.forClass(Domain.class);
        Mockito.verify(dao, Mockito.times(1)).insertDomain(domainCaptor.capture());
        Domain testDomain = domainCaptor.getValue();
        Assert.assertNotNull(testDomain);
        Assert.assertEquals(0, testDomain.getId());
        Assert.assertEquals("testValue", testDomain.getField2());
    }
}
Now, let's go through the important code sections one by one to explain what each of them do.

@RunWith(MockitoJUnitRunner.class)
To use any Mockito classes in your JUnit tests you have to use the Mockito JUnit Test Runner
Dao dao = Mockito.mock(Dao.class);
Verification methods can only be used on Mocked classes.
Mockito.when(dao.getDomain(Matchers.eq(1))).thenReturn(d);
Not strictly necessary since the default setup for a mocked method is to return null but it would be necessary for testing the update method.
Mockito.verify(dao, Mockito.never()).updateDomain(Matchers.anyObject());
This is the first part of the verification that this call does exactly what we want. Since I should be doing an insert then the update method shouldn't ever be called. This method call ensures that the method wasn't called.
ArgumentCaptor domainCaptor = ArgumentCaptor.forClass(Domain.class);
ArgumentCaptors are how you can retrieve objects that were passed into a method call (checking the return value would be silly since you would have set what the return value would be when setting up the mock object to begin with.)
Mockito.verify(dao, Mockito.times(1)).insertDomain(domainCaptor.capture());
This is the second part of the verification where you do two things:

  • Verify that the insert method was called and only called once
  • capture the object that was passed into the Dao.insertDomain() method
Domain testDomain = domainCaptor.getValue();
ArgumentCaptor is type sensitive so the getValue() method will return the appropriate type of object you captured. This is how you retrieve the object to check.

Issues

The main thing you need to watch out for when doing verify checks is that the call counts are cumulative from when you create the mock object. If I were to test the update method after my last Assert statement and I tried to do a verify on the insert method with a Mockito.never() for the number of calls it would throw an error saying that it was called once because of the last call. To fix this I generally suggest you test each of the states in separate test methods but if you have to do it in a single test method you'll have to either re-create the mock object or use the Mockito.reset() method which does the same thing (you'll have to re-set all the setup either way)

Conclusion

Using Mockito to mock out objects you're not directly testing makes writing unit tests much easier and more suscient. Using the verify() method with captors is a very handy way to tighten the tests to ensure the exact code flow you want to test was the code flow that ran. There are a few issues  you have to look out for but it gives you a powerful tool for ensuring tests are complete.