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);
}
}
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);
}
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());
}
}
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:
Post a Comment