Improve your unit-tests with jMock2

3

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.

Share.

About Author

3 Comments

  1. I started with JMock and then i moved to EasyMock and never look back, the way i see it its just API. Both have almost the same feauters – with JMock2 you use anonymous inter classes with EasyMock static imports. @RunWith may make EasyMock better.

  2. @RunWith(JMock.class) is a problem.

    How can you use jmock mocks with this:
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration

    My answer: EasyMock. Unless you have another way?