Converting multi-argument matches() from EasyMock 1 ArgumentsMatcher to EasyMock 2/3 IArgumentMatcher

StackOverflow https://stackoverflow.com/questions/21149572

  •  28-09-2022
  •  | 
  •  

Question

I have been assigned the task of upgrading our mocking code from EasyMock 1-style to EasyMock 2/3-style (and it's about time, given that large portions of EasyMock 1 were deprecated in 2005 and removed in 2010, yet we're still using it in 2014!).

I've figured out how to upgrade most things, but I'm having a lot of trouble in converting a matcher implementing ArgumentsMatcher (called a ParameterMatcher in EasyMock 1.0.1, which we were using) to a class implementing IArgumentMatcher.

In EasyMock 1's ArgumentsMatcher, the method signature for matches() was:

matches(Object[] expected, Object[] actual)

But the method signature for matches() in EasyMock 2/3's IArgumentMatcher is:

matches(Object argument)

I found out from a tutorial that with an IArgumentsMatcher, you can convert the matcher by moving the expected argument into a constructor, like so:

public class GenericMatcher implements IArgumentMatcher {

    private Object expected;

    public GenericMatcher(Object expected) {
        this.expected = expected;
    }

    public boolean matches(Object actual) {
        return this.expected.equals(actual); //Or some other comparison
    }
}

This works fine, but only if the arrays passed in contained one element. I have a number of matchers in the code that apparently matches up multiple elements at once. For instance:

public boolean matches(Object[] expected, Object[] actual) {
    if (expected[0].equals(actual[0]){
        return expected[1].getName().equals(actual[1]).getName());
    }
    else {
        return false;
    }
}

I have no idea how to convert this to IArgumentMatcher. While I could put multiple arguments in the constructor, the IArgumentMatcher interface only declares the match() method to have one argument, thus I can't do multiple comparison.

Naturally, the code I've inherited is undocumented and unfortunately the EasyMock documentation seems a bit lacking about what is actually passed into the matches method in either version. So how would I convert this matcher?

Was it helpful?

Solution

After a fair amount of looking at existing code and manipulating things in pieces, I figured out what was going on.

First, we'll take a step back to where this matcher is called in the EasyMock 1 code:

myMock.find("Testing", new DateSearchCriteria());
myControl.setMatcher(new GenericMatcher());
myControl.setReturnValue(AppConstants.TODAY);

Two things here: first there are two arguments for the find method. Second, there is only one matcher being assigned. With a bit of trial and error, I discovered that the EasyMock 1 ArgumentMatcher version of matches() takes an array because it's comparing all of the arguments in the call. So in this case, Object[] expected = ["Testing", new DateSearchCriteria()]. The custom matcher in the example from the question checks if the first argument is equal and the second argument has the same name, with an implicit understanding that the first argument will be a String and the second will be a DateSearchCriteria.

This isn't a great way to implement a matcher because if you do any refactoring, like changing the method signature or changing the implementation of DateSearchCriteria, the matcher will break. But since EasyMock 1 only let you set one matcher per method, this was the only way to match things.

EasyMock 2 and 3 improves the functionality so that you can set a matcher for each individual argument, rather than once for the whole method. This is why anIArgumentMatcher matches() now just takes an Object, rather than an Object array; because it's only examining one argument rather than all of them at once. So the EasyMock 1 code above would be as follows in EasyMock 2/3:

expect(persistenceManager.find(eq("Testing"), eqName(new DateSearchCriteria())))
    .setReturnValue(AppConstants.TODAY));

The eq() method is a matcher for equality built into EasyMock. The eqName() method would be a custom method implemented like so:

public <T> T eqName(T in) {
    reportMatcher(new NameMatcher(in));
    return null;
}

And NameMatcher would be implemented by making the same check that the old matcher did on argument #1:

public class NameMatcher implements IArgumentMatcher {
    private Object expected;

    public NameMatcher(Object expected) {
        this.expected = expected;
    }

    @Override
    public void appendTo(StringBuffer buffer) {
        buffer.appendTo("Name is \"" + expected.getName() + "\"");
    }

    @Override
    public boolean matches(Object actual) {
        return expected.getName().equals(actual.getName());
    }
}

So to sum it all up, a multi-argument matches() method like in EasyMock 1 where it is doing comparisons on each element of the inputted array is in fact checking each argument of the method that is being mocked. It is making assumptions about the state of each argument and is prone to breaking if you refactor. EasyMock 2 instead makes matchers per-argument instead of per-method. So what you need to do to convert an EasyMock 1 ArgumentsMatcher to an EasyMock 2/3 IArgumentMatcher is to basically split apart each argument match into its own matcher. In the example above, the old matcher tested for equality on argument 0 and an equal name on argument 1. So instead when you declare the method mock, you put an equality matcher on argument 0 (which is built in to EasyMock 2/3) and create your own name matcher for argument 1. They don't share matchers, they are individual, self-contained matchers.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top