Saturday, September 6, 2008

Improve your unit-tests with jMock2

Writing unit-tests should be part of your development process whether you write them before or after the actual coding I leave that up to you. On of the pitfalls of writing unit tests is that the units become to big.

The unit your are testing, a method in most cases probably uses some other beans or services to do its job. In your unit test you don’t want to test those other beans but just your method. Because you use decoupling Smiley you can easily override the interfaces of the services in your test case and inject those services in your instance you are testing. This is an example of a mock object.

This is maybe good for one or two mock objects but it gets tricky. Who test the mock objects? You will soon get a lot of logic in the mock objects. To reuse them they need to be extracted from the unit test. You don’t have checks if the mock objects are really called. There are several frameworks that will do this mocking for you. You supply them the interface and they will give you an implementation which can be injected into the bean thats being tested.....

Recently (14 jul 2008) jMock has released their 2.5.0 version of the lightweight mock framework. In the next example I shortly demonstrate how jMock2 can be used to improve your unit test.

In the example I want to test the registerUser method on the RegistrationBean. The bean will only create a new user if the username and password are not blank (they must contain a non white space character). The method will return true if the user is created otherwise false. The method uses to other references. The references need to be mocked out in the unit test. 

@Stateless
public class RegistrationBean implements RegistrationLocal {
@EJB
private NotificationMailerLocal notificationMailerBean;
@PersistenceContext
private EntityManager em;
public boolean registerUser(String username, String passwd) {
if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(passwd)) {
User user = new User(username, passwd);
em.persist(user);
notificationMailerBean.mailNewRegistration(user);
return true;
}
return false;
}

}

I use the Junit4 framework to write the tests.
To tell Junit4 I use jMock the @RunWith is added on top of the test class. The test checks if a user is created when a non blank username and password is supplied. jMock uses expectations. If you don’t add them and the mocked object is called the test will fail. With expectations we can tell jMock that we expect that a mocked method is called exactly once or we don’t care. There are many more expectations you can add.
But in this example we expect that the notification mail is send exactly once with a non null user object. We also tell Jmock that we are not interested in the calls to the em mock object.

@RunWith(JMock.class)
public class RegistrationBeanTest {
Mockery mockContext = new JUnit4Mockery();
@Test
public void registerUser() {
final NotificationMailerLocal mailer = mockContext
.mock(NotificationMailerLocal.class);
final EntityManager em = mockContext.mock(EntityManager.class);
mockContext.checking(new Expectations() {
{
ignoring(em);
one(mailer).mailNewRegistration(with(aNonNull(User.class)));
}
});
RegistrationBean regBean = new RegistrationBean();
regBean.setNotificationmailer(mailer);
regBean.setEntityManager(em);
assertTrue(regBean.registerUser("TestUser", "qwerty"));
}

}

In the second test I want to test that the user is not created when we supply an empty string as password. The registerUser method should return false. But I also want to make sure that the registration mail is not sent.

@Test
public void registerInvalidUser() {
final NotificationMailerLocal mailer = mockContext
.mock(NotificationMailerLocal.class);
final EntityManager em = mockContext.mock(EntityManager.class);
mockContext.checking(new Expectations() {
{
never(em);
never(mailer);
}
});
RegistrationBean regBean = new RegistrationBean();
regBean.setNotificationmailer(mailer);
regBean.setEntityManager(em);
assertFalse(regBean.registerUser("TestUser", ""));
}

Another problem I often have with mock objects is how to tell the mocked object what it should return. For this I changed the check in the registrationBean a little. It now uses a other bean that checks if the password is strong enough.

if (StringUtils.isNotBlank(username) && passwordCheckerBean.checkPass(passwd)) {

}

In the test we now also need a mock object for the passwordCheckerBean. But this time the mocked object should return a value. With Jmock you can also express this in a Expectation. The expectations part says that the checkPass method on the passChecker mock object is called only once with any String object. If it is called it will return true.


final PasswordCheckerLocal passChecker = mockContext.mock(PasswordCheckerLocal.class);
mockContext.checking(new Expectations() {
{

one(passChecker).checkPass(with(any(String.class)));
will(returnValue(true));
}
});

regBean.setPasswordChecker(passChecker);

For more about adding expectations to your mock objects go to the http://www.jmock.org site.
They have a quick getting started but also an useful cheat sheet http://www.jmock.org/cheat-sheet.html.

by Alan van Dam

0 comments: