Pages

Wednesday, November 11, 2009

Using argument matchers in EasyMock and mockito

I spent some quality time with EasyMock today while coaching some developers on building tests around an existing codebase. We were mocking a dependency of our SUT and the method call that we wanted to mock had two parameters. When we wrote the expectation, we used the anyObject() matcher for the first parameter and tried to perform an exact match on the second parameter, a Boolean. Here is an example of what we were trying to do in EasyMock (NOTE: we statically imported EasyMock so we could reduce the amount of code noise by not prefacing our expect, matcher, replay and verify invocations with EasyMock.):


expect(mockObject.retrieveSomething((String) anyObject(), false)).andReturn(someObject);


Unfortunately, EasyMock and mockito do not like this. They both want you to use matchers for all parameters if you use matchers for any parameters. However, the two libraries react quite differently when this situation occurs. EasyMock complains with a somewhat confusing message that at first blush makes it seem like we declared the expectation for multiple invocations. It really threw us off for a while (at least an hour) trying to figure out what was wrong with our expectations. Here is how we fixed it in EasyMock:


expect(mockObject.retrieveSomething((String) anyObject(), eq(false))).andReturn(someObject);


Mockito does a much better job of stating that when you use a parameter argument matcher in your expectation, you have to use parameter argument matchers for all of the parameters of the method call participating in the expectation. I find it interesting that mockito retains the behavior of EasyMock (mockito is a fork of EasyMock) with regards to argument matching, but mockito improves on the error messaging when something goes wrong with the mock object setup. Further reinforces my decision to forego EasyMock in favor of mockito.

5 comments:

  1. and what about Groovy testing?? :)

    MockFor mockObject = new MockFor(ObjectBeingMocked)

    def numberOfTimes = 1
    mockObject.demand.retrieveSomething(numberOfTimes) { param1, param2 ->
    assertFalse param2
    return someObject
    }

    serviceUnderTest.object = mockObject.proxyDelegateInstance()

    serviceUnderTest.doSomething()

    mockObject.verify serviceUnderTest.object

    ReplyDelete
  2. Mike,

    I'd love to introduce Groovy testing, but there's no one there that can take on the role of Groovy champion and guru. Not much more I can reveal. Thanks for the thoughts. I would definitely push for using Groovy for testing if I were to have a voice on a project that was looking to use testing to drive design.

    -- chris --

    ReplyDelete
  3. Hi, I'm working on EasyMock. Both frameworks are behaving like this because it's impossible technically to do otherwise. Even I get misled once in a while. The thing is that if some matchers are missing, you can't guess on which parameters they should be. You just have a list of matchers and a list of parameters.

    Then, it's interesting to know that Mockito gives a better error message. I'll need to check because I know there was a good (technical) reason for EasyMock to behave like it does.

    ReplyDelete
  4. Henri,

    Thanks for your note. Interestingly, I just had the same issue hit me in mockito (you'd think I'd learn after so many times of hitting this). Here's the stack trace you get from mockito when you don't use the argument matchers consistently in your expectation:

    org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
    Invalid use of argument matchers!
    3 matchers expected, 1 recorded.
    This exception may occur if matchers are combined with raw values:
    //incorrect:
    someMethod(anyObject(), "raw String");
    When using matchers, all arguments have to be provided by matchers.
    For example:
    //correct:
    someMethod(anyObject(), eq("String by matcher"));

    For more info see javadoc for Matchers class.

    Mockito makes it quite obvious what your issue is.

    -- chris --

    ReplyDelete