Monday, April 25, 2011

Using JUnit To Generate Mockito Custom ArgumentMatchers Descriptions

I'm a huge fan of Mockito. It has made our unit testing lives a veritable cakewalk for years now. There is something I'd like to be different, and that's what it provides for failure descriptions in custom argument matchers. It seems like a lot of work (well, relatively speaking) to provide a description then return false, assuming a test fails. It works out to about four lines of code for each value to be tested, if you use it something like this:

class IsPopulatedDataHolder extends ArgumentMatcher<DataHolder> {

private final int expectedValue;
private String failure;

public IsPopulatedDataHolder(int expectedValue) {
this.expectedValue = expectedValue;
}

@Override
public boolean matches(Object argument) {
DataHolder holder = (DataHolder) argument;
if(holder.getValue() != expectedValue) {
failure = "Held value does not match. Expected " + expectedValue + " but was " + holder.getValue();
return false;
}
return true;
}

@Override
public void describeTo(Description description) {
description.appendText(":" + failure);
}
}

Look at the description it builds. It looks suspiciously like a JUnit assertion message. We should let JUnit build the message for us. You can just throw the AssertionError out of matches(), but then you lose the nice Mockito-provided stack trace that hooks in with your IDE.

Have a look at this extension of the ArgumentMatcher. We'll catch the AssertionError, and stuff the message into Mockito's failure description:

public abstract class AssertConvertingArgumentMatcher<T> extends ArgumentMatcher<T> {

private String failure = null;

@Override
public void describeTo(Description description) {
description.appendText(failure);
}

@SuppressWarnings("unchecked")
@Override
public boolean matches(Object argument) {
try {
verify((T) argument);
} catch (AssertionError e) {
failure = e.getMessage();
return false;
}
return true;
}

protected abstract void verify(T argument);
}

Given this class, we can now use a JUnit Assert to build the string for us with a one-liner:

class IsPopulatedDataHolder extends AssertConvertingArgumentMatcher<DataHolder> {

private final int expectedValue;

public IsPopulatedDataHolder(int expectedValue) {
this.expectedValue = expectedValue;
}

@Override
protected void verify(DataHolder argument) {
Assert.assertEquals("Held value", expectedValue, argument.getValue());
}
}

Normally if we use an Assert in matches(), we lose the failure stack that Mockito so nicely provides for us. AssertConvertingArgumentMatcher:
  • Ties together the descriptive JUnit error with the Mockito custom matcher
  • Reduces lines to test the target code
  • Keeps intact the standard mockito stack trace (double-click drill-into-test, double-click drill-into-target)
  • Uses JUnit to build "Line width expected: <2.0> but was <1.0>," and this means no hand-coding of descriptions. JUnit builds the description for us.
  • Assert is one line instead of if(this) etc,etc
I've put some code samples here:

git://github.com/DonBranson/MockitoMatcherExamples.git

No comments: