#35 Easier argument matchers

EasyMock_3.0
open
EasyMock (33)
5
2012-10-05
2010-06-05
Henri Tremblay
No

(submitted by Bruce Brouwer)

I've always found it to be a pain to write EasyMock IArgumentMatcher classes. Then I noticed easymock-propertyutils, and it gave me an idea. What if there was an easier way writer matchers, and have them survive refactorings.

I have to admit, with the addition of the Capture objects, I don't usually need argument matchers, but there are times when they are still necessary. Wouldn't it be nice if I could write this:

MyService some = createMock(MyService.class);
MyParameter param = createArgumentMatcher(MyParameter.class); // new API
expect(some.methodCall(eq(param, param.getString(), "hi"))).andReturn("result1");
expect(some.methodCall(eq(param, param.getString(), "there"))).andReturn("result2");

What this would do is create a matcher that matches param when param.toString() equals "hi". Another matcher is created to match when param.toString() equals "there". Notice how param is a matcher that is a proxy object (just like mocks themselves) used for recording what method the matcher needs to call, and what
the expected value is.

You could even put multiple tests on the matcher like this:

expect(some.methodCall(and(eq(param, param.getString(), "hi"), eq(param, param.getInt(), 1))).andReturn("result3");

This creates a matcher for param that matches when param.getString() equals "hi" and param.getInt() equals 1.

You could even take this a step further so that you could dig into
sub-properties:

expect(some.methodCall(eq(param, param.getParent().getId(),
"parent-id"))).andReturn("result4");

Notice how this is going multiple levels deep to match
param.getParent().getId().

While we're at it, why not add a new static method called argThat which takes Hamcrest matchers:

expect(some.methodCall(argThat(param, param.getString(), equalTo("hi"))).andReturn("result5");

Of course, this would require some additions to EasyMock. For one, that createArgumentMatcher(Class<?> classToMatch) method, as well as additional versions of all the static methods that create matchers today, like eq, lt, ...

The things I like is that I don't have to write any matcher classes, and it will survive some types of refactoring.

I don't think that this would have to change the core of EasyMock in any way.

I don't have anything like this written yet, but I was wondering if others thought that this would be useful.

Discussion


  • Anonymous
    2010-06-06

    The reason I made the signature for eq to be used like this:

    eq(param, param.getString(), "hi")

    was so there would be no problems matching the argument type. This would make the signature of eq this:

    public static <T, P=""> T eq(T matcher, P actual, P expected)

    It would be nicer if I didn't actually have to pass in the matcher, making the call look like this:

    eq(param.toString(), "hi")

    The problem with this is that the return type of eq cannot be inferred from the parameters being passed into eq. That is why I included the matcher originally in the request. To use this alternate method would make the signature of eq this:

    public static <T, P=""> T eq(P actual, P expected)

    I've at times had trouble with Java guessing the correct return type for existing matchers like anyObject(), I've had to resort to a type cast. But maybe it's worth the occasional typecast if it makes the syntax more concise.

    Also, this new argument matcher object should really be treated as an argument matcher factory, really. Even in the original examples, it actually produces multiple argument matcher instances. Maybe a better name for it would be createArgumentRecorder, and that thing assists in creating argumentMatchers through the methods like eq(...).