Monday, November 30, 2009

Write Readable Tests with Mockito

A good mocking framework is a must have in any Java developers toolset. Mocking frameworks greatly reduce the amount of code needed to effectively mock dependent interfaces and/or classes. Over the past several years EasyMock has been my mocking framework of choice. But, the framework has a couple of issues that could be improved upon. First, tests using EasyMock tend to be less readable than desired, and second, when tests fail, it's often extremely difficult to decipher the exact problem.

A couple months ago I came across mockito. The docs promised me the ability to "write beautiful tests" that are "more readable" and "produce clean verification errors." Perfect, I thought I'd give Mockito a try. Now, after several months of using mockito on a large project, I can say that I have a new favorite mocking framework.

Let's take a simple example to illustrate the benefits of mockito. In this example, we have a PersonService with the method updateName(Integer personId, String name). In order to update the name, the PersonService will retrieve the existing Person via a call to PersonDao.fetchPerson(Integer personId), create a new instance of the immutable Person with the new name value set, and finally call PersonDao.update(Person) to save the changes. I realize this is not the most performant means of updating the name in the database, but it provides a nice simple example for illustration purposes.

First, take a look at the Person class. Nothing special, just a simple bean.

public class Person {

private final Integer id;
private final String name;

public Person(Integer id, String name) {
this.id = id;
this.name = name;
}

public Integer getId() {
return id;
}

public String getName() {
return name;
}

}



And, here's the PersonDao interface. I've left out the implementation to simplify the example. Although, do note that mockito will mock classes as well as interfaces.

public interface PersonDao {

public Person fetchPerson(Integer id);

public void update(Person person);

}



Finally, here's the PersonService. The class we'll be testing.

public class PersonService {

private final PersonDao personDao;

public PersonService(PersonDao personDao) {
this.personDao = personDao;
}

public boolean update(Integer personId, String name) {

Person person = personDao.fetchPerson(personId);
if (person != null) {
Person updatedPerson = new Person(person.getId(), name);
personDao.update(updatedPerson);
return true;
} else {
return false;
}

}

}



Now, for our tests. The first snippet shows the static imports defined in the test class. The static imports improve the readability of tests by allowing the code to read like a DSL. I just wish eclipse would default to allow .* when organizing static imports. There is a setting, but you must change it with each new workspace.

import static org.mockito.Mockito.*;
import static junit.framework.Assert.*;


Below, you'll see the first sign of mockito. In this case, the before() method creates a mock of the personDao by calling mock(PersonDao.class). Nice and easy.

public class PersonServiceTest {

private PersonService personService;
private PersonDao personDao;

@Before
public void before() {
this.personDao = mock(PersonDao.class);
this.personService = new PersonService(personDao);
}
...
}


Below is our first test. A happy path scenario in which the PersonService finds the existing Person and calls the PersonDao to update the Person's name. Let's take a closer look at what's happening in the test.

  • The personDao.fetchPerson() method is stubbed simply by calling when(personDao.fetchPerson(1)).thenReturn(person). This statement informs the mock PersonDao to return person when the fetchPerson method is called with a value of 1.
  • The statement verify(personDao).fetchPerson(1) verifies that personDao.fetchPerson is called with a value of 1. If it is not, the test will fail.
  • The ArgumentCaptor provides a really slick way to capture the variable passed to the update method of the PersonDao. And, easily allows me to assert that the name has changed to the new value.
  • The verifyNoMoreInteractions(personDao) statement asserts that during the test, there are no other calls to the mock object.


    @Test
public void shouldUpdatePersonName() {

Person person = new Person(1, "john");
when(personDao.fetchPerson(1)).thenReturn(person);

boolean updated = personService.update(1, "joe");
assertTrue(updated);

verify(personDao).fetchPerson(1);

ArgumentCaptor<Person> personCaptor = ArgumentCaptor
.forClass(Person.class);
verify(personDao).update(personCaptor.capture());
Person updatedPerson = personCaptor.getValue();
assertEquals("joe", updatedPerson.getName());

verifyNoMoreInteractions(personDao);

}


The second scenario we'll test is shown below. It proves that when PersonDao.fetchPerson(Integer) returns null, PersonDao.update(Person) is not called. Nothing really new here. Just notice that the stubbed call (when...thenReturn), is returning null, and the test is no longer verifying that personDao.update(Person) is called.

    @Test
public void shouldNotUpdateIfPersonNotFound() {

when(personDao.fetchPerson(1)).thenReturn(null);

boolean updated = personService.update(1, "joe");
assertFalse(updated);

verify(personDao).fetchPerson(1);
verifyNoMoreInteractions(personDao);

}



Now, for our final test, let's see what happens when an unexpected call happens. This is the same test as above, but the test is no longer calling verify(personDao).fetchPerson(1). So, the test fails...

    @Test
public void shouldFailWithNiceMockitoError() {

when(personDao.fetchPerson(1)).thenReturn(null);

boolean updated = personService.update(1, "joe");
assertFalse(updated);

// notice no longer calling verify(personDao).fetchPerson(1);
verifyNoMoreInteractions(personDao);

}


And, check out the readability of the test failure! In very simple terms, the message tells me exactly what happened and where it went wrong.

org.mockito.exceptions.verification.NoInteractionsWanted: 
No interactions wanted here:
-> at com.opi.mock.PersonServiceTest.shouldFailWithNiceMockitoError(PersonServiceTest.java:70)
But found this interaction:
-> at com.opi.mock.PersonService.update(PersonService.java:20)

at com.opi.mock.PersonServiceTest.shouldFailWithNiceMockitoError(PersonServiceTest.java:70)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:59)
at org.junit.internal.runners.MethodRoadie.runTestMethod(MethodRoadie.java:98)
at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:79)
at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:87)
at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:77)
at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:42)
at org.junit.internal.runners.JUnit4ClassRunner.invokeTestMethod(JUnit4ClassRunner.java:88)
at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:51)
at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4ClassRunner.java:44)
at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:27)
at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:37)
at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4ClassRunner.java:42)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:46)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)




As you can see from these examples, mockito tests are simple to write, easy to read, and fail with nice concise messages. All factors leading to happy, productive developers! Happy mocking!

Wednesday, January 14, 2009