Realsolve Logo
Articles
 

Mock Object Testing With EasyMock 2

An introduction and tutorial on Mock Object testing using EasyMock.

By Phil Zoio, 21 April, 2007

In this article, I look closely at the EasyMock library. Long standing users of EasyMock will have noticed that the version 2.x API has been radically overhauled to use Java 5 generics. The result is a powerful and easy to use Mock Objects implementation, which really simplifies unit testing of classes with complex dependencies.

Introduction

An oft-stated goal of TDD is making sure that you have 100% coverage of your code base. In fact, so important, that a whole set of tools, both open source and commercial, have been spawned to help developers ensure that this goal is met.

To developers practicising TDD for the first time, some classes are fairly simple to test, while others are quite hard to test. The ones that are simple to test are those which have no external dependencies, so that they can easily be tested in isolation. More difficult to test are classes with external dependencies. A classic example is a Servlet, whose dependencies include Servlet API objects such as HttpServletRequest and HttpServletResponse, as well as application-specific interfaces and classes.

Prior to Java 1.3, there were two ways to test Servlets. Firstly, you could create stub libraries for the API classes, and use these to satisfy the dependencies of Servlets under test. Secondly, you could use in-container testing, where the dependencies would be satisfied by running the Servlets on a real servlet container. Neither solution was very satisfactory. Stub libraries require effort to set up and maintain, and in-container tests tend to be unwieldy and not very suitable for testing very fine grained operations.

Since then, a new testing technique has been introduced which for many practitioners of TDD has revolutionised their approach to testing. This technique is known as Mock Objects, perhaps a rather unhelpful name because it leads so easily to confusion with stub libraries which are also called "mocks", but are very different. Ultimately, the only similarity between Mock Objects and stubs is that they share a common purpose in helping to resolve dependencies for unit testing. The way they work is quite different.

The Example Application.
For concrete examples of the some of the points described below, take a look at the example application. Here you'll find working examples which back the source code found in this article, which you can also set up as an Eclipse project.

EasyMock Basics

The basic idea behind Mock Objects is that dependencies are resolved on the fly within the test itself. To illustrate our point, we'll use an example that takes us back a few years, in Java web development terms, to the days before web frameworks were invented. Our example offers little more functionality than a HelloWorld Servlet. It takes two HttpServletRequest parameters, firstName and lastName, and uses these to set two equivalently named request attributes. Control is then forwarded to a JSP, which in turn outputs something like Hello Phil Zoio. A trivial example, of course, but sufficient to demonstrate the main features of EasyMock.

public class NameServlet extends HttpServlet
{
  private static final long serialVersionUID = 1L;

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse res) 
  	throws ServletException, IOException
  {
    String firstName = req.getParameter("firstName");
    String lastName = req.getParameter("lastName");
    
    if (firstName != null) req.setAttribute("firstName", firstName);
    if (lastName != null) req.setAttribute("lastName", lastName);
    
    req.getRequestDispatcher("/hello.jsp");
  }
}

Now lets consider testing of this class. What do we want to test, and how do we go about testing it. We are particularly interested in the way that our implementation class collaborates with its dependencies. We want to make sure that the our class behaves correctly, and in the way we expect, when interacting with these parameters. We want make sure that the right methods are called in the right order with the right parameters. In our example Servlet, recognising the dependencies is easy. The sole dependency is HttpServletRequest. HttpServletResponse is present as a method parameter, but it is not used.

Obviously, in order to test our Servlet's behaviour, we need to give it something to respond to. We need to "create" or simulate the behaviour of HttpServletRequest instance. Mock Objects, and EasyMock in particular, not only make it easy to do simulate collaborator behaviour, but make it easy to verify that our class behaves as we expect.

Let's consider an example JUnit test which shows the EasyMock at work.

import static org.easymock.EasyMock.*;
//other imports omitted

public class NameServletTest extends TestCase
{
    public void testWithMockObjects() throws Exception
    {
        // strict mock forces you to specify the correct order of method calls
        HttpServletRequest request = createStrictMock(HttpServletRequest.class);

        expect(request.getParameter("firstName")).andReturn("phil");
        expect(request.getParameter("lastName")).andReturn("zoio");

        request.setAttribute("firstName", "phil");
        request.setAttribute("lastName", "zoio");
        
        expect(request.getRequestDispatcher("/hello.jsp")).
        	andReturn(createMock(RequestDispatcher.class));
        
        //unexpected method calls after this point will throw exceptions
        replay(request);
        
        new NameServlet().doPost(request, null);
        
        //check that the behaviour expected is actually
        verify(request);
    }
    
    //more test methods omitted ...
}

An EasyMock test consists of two distinct stages: an expectations setting stage and a replay stage. The expectation setting stage begins with the creation of Mock objects using one of the static methods of the EasyMock class. We use the method EasyMock.createStrictMock() to create a HttpServletRequest instance. Note that createStrictMock() has the signature

public static <T> T createStrictMock(Class<T> toMock)

This use of Java 5 generics frees you from having to cast your proxy to the correct type, and improvement over the EasyMock 1.x API. Another Java 5 feature which turns out to be quite convenient for EasyMock-based tests is static imports, which free you from having to qualify calls to EasyMock static methods.

import static org.easymock.EasyMock.*;

Creation of the mock object is thus as simple as the call:

HttpServletRequest request = createStrictMock(HttpServletRequest.class);

Once created, we use the API of the mock object's interface to specify expectations and behaviour, as shown by the line below.

expect(request.getParameter("firstName")).andReturn("phil");

The line of code above is almost self-explanatory. An HttpServletRequest.getParameter() call is expected, with the parameter "firstName". The second part of the statement specifies behaviour for the mock object, here we are simply telling our mock object to return the value "phil".

The lines that follow proceed in a similar way. Note that the void method setAttribute() does not require any behaviour to be specified, so it is not wrapped using an expect(...).andReturn(...) combination.

The transition from the expectation setting stage is triggered using the call replay(request). Essentially, this tells EasyMock that we no longer wish to record expected HttpServletRequest method calls. At this point the method under test is invoked. During this stage, the behaviour we specified for the collaborators is replayed in response to calls made to it. Returning to the our doPost() implementation, it is clear that the first method to be called is getParameter("phil"). Based on the behaviour we have specified, our mock HttpServletRequest will faithfully return "zoio". The process will continue in this way until doPost() returns.

So what happens if a method is called on the mock object for which no expectation has been set during the expectation setting state? We can see an example of this by commenting out the line expect(request.getParameter("firstName")).andReturn("phil"). The answer depends on the type of mock object we use. In our example, we created a "strict" mock, which as the name suggests, is not very forgiving when it encounters behaviour for which no expecation has been set, and an AssertionFailedError will be thrown.

Similarly, what if you record expectations for calls which are not made. For example, suppose you add the line

expect(request.getParameter("notCalled")).andReturn(null);

prior to the replay(request) call. EasyMock has no way of determining during the execution of doPost() that this method is called, so an extra step is necessary to allow EasyMock to figure out that an invalid expectation has been set. This is the role of the verify(request) method call. Comment this out, and you'll notice the test pass in spite of the redundant expectation. The verify() method is used to make sure that all of the behaviour expected from the collaborators is valid.

The reset() Method

In the example above, the test method has a single replay(...) and verify(...) call, with single expectation setting and verification phases. Sometimes it is convenient to reuse the same mock object over subsequent expectation setting and verification cycles. In this case, the EasyMock.reset() method is handy. It allows us to set the mock object back to expectation setting mode. The example below illustrates.

public void testWithReset() throws Exception
{
  HttpServletRequest request = createStrictMock(HttpServletRequest.class);
  doTest(request);

  reset(request);

  doTest(request);
}

private void doTest(HttpServletRequest request) throws ServletException, IOException
{
  expect(request.getParameter("firstName")).andReturn(null);
  expect(request.getParameter("lastName")).andReturn(null);
  expect(request.getRequestDispatcher("/hello.jsp")).
  	andReturn(createMock(RequestDispatcher.class));
  replay(request);
  new NameServlet().doPost(request, null);
  verify(request);
}

Notice how we have extracted an expectation setting and verification cycle into the doTest(...) method. Notice how this method is called twice using the same mock object in our testWithReset() method, demonstrating how we can use reset() on the mock to set it back to its original state.

Multiple Methods

Another common scenario is for the same method on a mock object to be called multiple times, potentially with the same parameter values. A convenient shortcut for expressing this is the times() method, which is shown in action in the example below.

public void testWithTimes() throws Exception
{
  // strict mock forces you to specify the correct order of method calls
  HttpServletRequest request = createStrictMock(HttpServletRequest.class);

  expect(request.getParameter(isA(String.class))).andReturn("not telling you").times(2);
  request.setAttribute(isA(String.class), eq("not telling you"));
  expectLastCall().times(2);

  expect(request.getRequestDispatcher("/hello.jsp")).
  	andReturn(createMock(RequestDispatcher.class));

  replay(request);

  new NameServlet().doPost(request, null);

  verify(request);
}

Note that instead of setting an expectation for each invocation of getParameter() and setAttribute(), we collapsed each of our pairs of expectations into a single statement using the times(2) method. Note, of course, that our test is weaker than in the original strict mock test; it simply asserts for example that getParameter() is called twice, without specifying which parameter values are supplied. Nevertheless, this capability can be useful for some tests.

Throwing Exceptions

EasyMock provides a simple mechanism for setting expectations for thrown exceptions. The following example shows an expected RuntimeException thrown by the request mock's getRequestDispatcher method.

expect(request.getRequestDispatcher("/hello.jsp")).
	andThrow(new RuntimeException());

For methods returning void, the construct has the form:

expectLastCall().andThrow(someThrowableInstance);

Nice Mocks and Strict Mocks

You may have noticed that our example above used the method createStrictMock() to create the mock objects used. There are two other flavours of Mock objects supported by EasyMock. One of these goes by the term nice mocks, while the other is simple the default Mock object. The three different types of mocks objects differ in two respects:

We can illustrate the optionality using the following example, which is a valid if not particularly useful test using nice mocks.

public void testWithEmptyNiceMock() throws Exception
{
  // failure to set expected methods is not picked up because we're using nice mock
  HttpServletRequest request = createNiceMock(HttpServletRequest.class);

  replay(request);

  new NameServlet().doPost(request, null);

  verify(request);
}

Note that even though methods are called on the request, our test still passes even though we have not specified any behaviour for the mock object. What actually occurs, is that each invocation of request.getParameter(...) simply returns null.

The flexibility with the ordering of expectations, which is present for nice and default mocks, is evident in the example below:

public void testWithMock() throws Exception
{
  // ordering not enforced, because we're not using strict mock
  HttpServletRequest request = createMock(HttpServletRequest.class);

  expect(request.getRequestDispatcher("/hello.jsp")).
  	andReturn(createMock(RequestDispatcher.class));
  expect(request.getParameter("lastName")).andReturn(null);
  expect(request.getParameter("firstName")).andReturn(null);

  replay(request);

  new NameServlet().doPost(request, null);

  verify(request);
}

Note how our expectation for request.getRequestDispatcher(...) is stated before the request.getParameter(...) expectations, even though the actual order of invocation in the Servlet's doPost(...) method is in reverse. As long as the methods which are expected are actually called, a default (or nice) mock won't complain about the order in which these expectations are stated.

Stubs

Strict mocks are great for defining really rigorous tests, but there are times when applying them can lead to overkill. Suppose for example that your mock objects contain a bunch of methods, with only some of these of interest. How do you stop your tests from failing on the as a result of unexpected behaviour of these "other" methods. This is where Stubs come to the rescue.

Consider the code snippet below.

HttpServletRequest request = createStrictMock(HttpServletRequest.class);
...
expect(request.getAttribute(isA(String.class))).andStubReturn("anything");

replay(request);
...
verify(request);

The last line says, "if request.getAttribute(...) is called once or more times, then each time simply return the String "anything". However, if the method is not called, then don't fall over". This gives us quite a lot of flexibility in stubbing out interaction with the mock objects which are irrelevant to the test at hand, without losing rigour in dealing with the interactions which count.

Argument Matchers

When EasyMock verifies expectations it checks that the methods that are expected to be called are actually called, possibly checking for correct ordering. But it does that more than that. It also checks the arguments that are passed into the method calls. For example, the expectation

expect(request.getParameter("firstName")).andReturn("phil");

will result in a check which ensures that request.getParameter() is called with the parameter "firstName". By default EasyMock uses object equality as a basis this test. In many cases, it is not desirable (or even possible) to use object equality to perform this check. This is where argument matchers come into the picture. EasyMock provides a whole bunch of argument matchers out of the box, exposed via static methods of the EasyMock class. In fact, most of the EasyMock static methods expose argument matchers. The conditions covered range from "the argument an instance of a particular class" to "the argument must be greater than a certain value" to "the argument must contain a specified String". It even allows for argument conditions to be negated using one of the not() methods or composed using the and() and or() methods.

We see a simple example of an argument matcher at work in the statement below.

expect(request.getAttribute(isA(String.class))).andReturn("someValue");

The isA() method provides a convenient way of weakening our expectation; all that we expect is that the argument should be an instance of String.

Most of the time the supplied argument matcher implementations will satisfy your needs, but of course there are times when they don't. Suppose for example that you are testing a method which creates three random Strings, adds them to a List, and then calls a collaborator with the results. This is what the code looks like.

interface CollectionHandler {
  public void handleCollection(Collection collection);
}

class ListGenerator {
  private CollectionHandler handler;

  public ListGenerator(CollectionHandler handler) {
    this.handler = handler;
  }

  void generateAndHandleCollection(int count) {
    List<String> list = new ArrayList<String>();
    for (int i = 0; i < count; i++) {
      list.add(UUID.randomUUID().toString());
    }
    handler.handleCollection(list);
  }
}

Ideally, we would like to test this behaviour, but cannot do this using any of the supplied argument matchers. Luckily, creating your own argument matcher is pretty straightforward. It involves two pieces - an implementation of IArgumentMatcher, and a static method which can be used it to expose it to tests. Our IArgumentMatcher implementation is shown below:

public class CollectionCountMatcher implements IArgumentMatcher
{
  private int expectedCount;

  public CollectionCountMatcher(int count) {
    super();
    this.expectedCount = count;
  }

  public boolean matches(Object actual)
  {
        return (actual instanceof Collection) 
          && ((Collection) actual).size() == expectedCount;
  }

  public void appendTo(StringBuffer buffer)
  {
    buffer.append("collectionContains(" + expectedCount + ")");
  }
}

The implementation requires that you implement two methods, matches(Object) to determine whether the test succeeds, and appendTo(StringBuffer) to assist in generating a failure message if the test fails.

The second part of the implementation is a static method to expose the new argument matching functionality, which we've added to the class Matchers in much the same way that other argument matchers are added to methods in the class EasyMock.

public class Matchers
{
  public static Collection collectionContains(int expectedSize)
  {
    EasyMock.reportMatcher(new CollectionCountMatcher(expectedSize));
    return null;
  }
}

With these two pieces in place, we can write a test which tests our class's behaviour.

public void testHandleCollection() {
  CollectionHandler mock = createMock(CollectionHandler.class);
  ListGenerator toTest = new ListGenerator(mock);

  mock.handleCollection(Matchers.collectionContains(3));
  replay(mock);

  toTest.generateAndHandleCollection(3);

  verify(mock);
}

Note that the collectionContains() argument matching test applies a more stringent test than a simple EasyMock.isA(Collection.class) call. However, it is weaker than an EasyMock.equals(...) matching call. Of course, an equals matching comparison could not be applied for our ListGenerator, because the Collection instance created contains random elements.

Summary

Mock objects are a powerful technique for helping to obtain high levels of test coverage for your code without much of the stress associated with resolving dependencies. It allows the behaviour of collaborators of your classes under test to be specified with relatively little effort and a great amount of control. EasyMock is a particular powerful framework for applying mock objects in practice.

The aim of this article has been to help new users overcome the initial hurdles necessary for putting EasyMock to work in testing their applications. It has also covered some details of the framework in action, starting with the simple use of EasyMock life cycle methods, moving through to more advance topics such as the use of custom argument matchers.

This is a working document. If there are any errors, or if you disagree with anything which I have said, or have any suggestions for improvements please email me at philzoio@realsolve.co.uk.