EasyMock: capturing arguments passed to mock objects

by Rommert de BruijnSeptember 22, 2008

EasyMock2 is a library that provides an easy way to create on-the-fly mock objects based on interfaces. It can be used in combination with JUnit to create simple and powerful unit tests. Here’s an overview of the basic EasyMock flow, followed by a brief example that demonstrates a new feature in EasyMock 2.4: capturing arguments passed to MockObjects.

EasyMock2 basics

Lets assume we’re testing a ManagerImpl object that requires a Dao object to do some work. The manager passes a String object to the dao, and receives an Integer in return. Since we only want to test the Manager object in isolation, it seems a good idea to mock the Dao object.

The Dao interface:

interface Dao {
   Integer poseQuestion(String question);
}

The Manager interface + implementation:

interface Manager {
   String poseQuestion(String question);
}

With implementation:

class ManagerImpl1 implements Manager {
   Dao dao;
   public String poseQuestion(String question) {
      return dao.poseQuestion(question).toString();
   }
   // Dao setter etc
}

EasyMock basically does the following:

  • Create a mock object from the Dao interface:
    Dao mockDao = createMock(Dao.class);
  • Inject the mock object where an actual implementation would have been used:
    manager.setDao(mockDao);
  • “Record” all expected method calls to this mock object, and define appropriate responses. So-called argument matchers are used to put constraints on the expected arguments passed to the Dao
    expect(mockDao.poseQuestion(  // the expected call
    	and(	// merges the 2 matchers below
    	   isA(String.class)),	// value is a String?
    	   eq("What's the meaning of life?"))  	// value equals given String?
       .andReturn(new Integer(42))	// define the return value for this method call
       .once();	// This call is expected only once.
  • When all expected calls are recorded, put the mock object in “replay” mode
    replay(mockDao);
  • In replay mode, call the manager which in turn will call the mock object in its natural habitat
    String theCorrectAnswer = manager.poseQuestion("What's the meaning of life?");
  • Afterwards: verify if all expected calls to the mock object were actually made.
    verify(mockDao)
  • Capturing arguments

    Now let’s say the manager in the above example does not pass the String argument directly to the Dao. Instead, it uses a Wrapper object as an argument for the dao method calls. The Wrapper object contains the String argument from the manager call, and some additional arguments only used in the Dao (e.g. for logging).

    interface Dao2 {
       Integer poseQuestion(Wrapper questionWrapper);
    }
    
    class ManagerImpl2 implements Manager {
       Dao2 dao;
    
       public String poseQuestion(String question) {
          Wrapper wrapper = new Wrapper();
          wrapper.setQuestion(question);
          wrapper.setDate(new Date());
          // wrapper.setMore(...)
    
          return dao.poseQuestion(wrapper).toString();
       }
    
       // Dao2 setter etc
    }
    

    To fully test this ManagerImpl2 class, we’d like to know exactly what arguments are stored in the Wrapper object when it’s passed to the dao. The isA(Wrapper.class) matcher is of no help: checking the type of the Wrapper argument is not enough in this case. We want to check the specific values inside it.

    EasyMock 2.4 offers a new feature to do just that. An additional capture(Capture c) matcher was added. This Matcher does not actually match anything, but stores the object passed to the mock object in a Capture object. Calling the getValue() on this Capture object later on will return the original argument. In this example, capturing the Wrapper object as it’s passed to the dao allows us to check whether it still contains the String that was used as an argument in the manager call.

    A complete unit test for ManagerImpl2 would contain a method that looks something like this:

    @Test
    public void testPoseQuestion() {
       ManagerImpl2 manager = new ManagerImpl2();
       Dao2 mockDao = createMock(Dao2.class);
    
       manager.setDao(mockDao);
    
       Capture capturedArgument = new Capture();
    
       expect(mockDao.poseQuestion(
          and(
             // capture the argument for later use
             capture(capturedArgument),
             // check if it is a Wrapper, as expected
             isA(Wrapper.class)))
       .andReturn(new Integer(42))
       .once();
    
       replay(mockDao);
    
       // mockDao is used during manager call,
       // the actual wrapper object is captured
       manager.poseQuestion("What's the meaning of life?");
    
       verify(mockDao);
    
       // Check the captured argument: does it contain the original
       // question, or was it modified by the manager somehow?
       assertEquals("What's the meaning of life?",
                ((Wrapper) capturedArgument.getValue()).getQuestion());
    }
    

    The capture matcher allows for easy argument checking, without the necessity to implement additional mock arguments with custom equals/hashCode methods.

    See the EasyMock2.4 documentation for more examples, and more useful tricks.