Site icon JVM Advent

Using Matchers in Tests

Gone are the days when we were forced to write way too many assertion lines in our testing code. There is a new sheriff in town: assertThat and his deputy: the matchers. Well, not that new, but anyway I’d like to present to you shortly how matchers are used and after that an extension to matchers concept that I found to be very useful when developing unit tests for my code.

First of all I’ll present the basic use of the matchers. Of course you can have a complete presentation of hamcrest matchers capabilities directly from its authors: https://code.google.com/p/hamcrest/wiki/Tutorial.

Basically a matcher is an object that defines when two objects match. The first question usually is why wouldn’t you use equals? Well, sometimes you don’t want to match two objects on all their fields, just on some of them and if you work with legacy code you’ll find that the equals implementation is not present or is not as you would’ve expected. Another reason is the fact that using assertThat gives you a more consistent way of “asserting the assertions” and arguably a more readable code. So, for example, instead of writing:


int expected, actual;
assertEquals(expected, actual);

you will write


assertThat(expected, is(actual));

where “is” is the statically imported org.hamcrest.core.Is.is

Not that much of a difference… yet. But Hamcrest offers you a lot of very useful matchers:
Now we’re making progress… still the power of Hamcrest matchers is that you have the possibility to write your own matchers for your objects. You just have to extend BaseMatcher<T> class. Here is an example of a simple custom matcher:


public class OrderMatcher extends BaseMatcher<Order> {
private final Order expected;
private final StringBuilder errors = new StringBuilder();

private OrderMatcher(Order expected) {
this.expected = expected;
}

@Override
public boolean matches(Object item) {
if (!(item instanceof Order)) {
errors.append("received item is not of Order type");
return false;
}
Order actual = (Order) item;
if (actual.getQuantity() != (expected.getQuantity())) {
errors.append("received item had quantity ").append(actual.getQuantity()).append(". Expected ").append(expected.getQuantity());
return false;
}
return true;
}

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

@Factory
public static OrderMatcher isOrder(Order expected) {
return new OrderMatcher(expected);
}
}

This is a completely new league compared to the old assertion methods.

So this is in a nutshell the usage of the Hamcrest’s matchers.

But, when I started using it in real life, especially when working with legacy code, I realized that there is more to the story. Here are some issues that I’ve encountered when using matchers:
  1. Matcher construction can be very repetitive and boring. I needed a way to apply DRY principle to matcher code.
  2. I needed an unified way to access the matchers. The correct matcher should be chosen by the framework by default.
  3. I needed to compare objects that had reference to another objects which should have been compared with matchers (the object referencing can go as deep as you want)
  4. I needed to check a collection of objects  using matchers without iterating that collection (doable also with the array matchers… but I wanted more J)
  5. I needed to have a more flexible matcher. For example, for the same object I needed to check one set of fields, but in another case another one. The out-of-box solution is to have a matcher for each case. Didn’t like that.

I’ve overcome these issues using a matcher hierarchy that some conventions and which knew which matcher to apply and which field to compare or ignore. At the root of this hierarchy is the RootMatcher<T> that extends BaseMatcher<T>.

To deal with the #1 issue (repetitive code), the RootMatcher class contains the common code for all the matchers like methods for checking if the actual is null, or it has the same type with the expected object, or even if they are the same instance:


public boolean checkIdentityType(Object received) {
if (received == expected) {
return true;
}
if (received == null || expected == null) {
return false;
}
if (!checkType(received)){
return false;
}
return true;
}
private boolean checkType(Object received) {
if (checkType && !getClass(received).equals(getClass(expected))) {
error.append("Expected ").append(expected.getClass()).append(" Received : ").append(received.getClass());
return false;
}
return true;
}

This will simplify the way the matchers are written, I don’t have to take into account null or identity corner cases; it’s all been taken care of in the root class.

Also the expected object and the errors reside in the root class:


public abstract class RootMatcher extends BaseMatcher {
protected T expected;
protected StringBuilder error = new StringBuilder("[Matcher : " + this.getClass().getName() + "] ");

This allows you to get to the matches method implementation as soon as you extend RootMatcher and for errors you just put the messages in the StringBuilder; RootMatcher will handle sending them to the JUnit framework to be presented to the user.

For issue #2 (automatic matcher finding) the solution was in its factory method:


@Factory
public static Matcher is(Object expected) {
return getMatcher(expected, true);
}
public static RootMatcher getMatcher(Object expected, boolean checkType) {
try {
Class matcherClass = Class.forName(expected.getClass().getName() + "Matcher");
Constructor constructor = matcherClass.getConstructor(expected.getClass());
return (RootMatcher) constructor.newInstance(expected);
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
}
return (RootMatcher) new EqualMatcher(expected);
}

As you can see the factory method tries to find out which matcher should it return by using two conventions
  1. The matcher for an object has the name of the object + the string Matcher
  2. The matcher is in the same package as the object to be matched (recommendable to be in the same package, but in the test directory)

Using this strategy I succeeded in using a single matcher: RootMatcher.is that will  provide me the exact matcher that I need

And to solve the recursive nature of the object relations (issue #3), when checking object fields I used the method from RootManager to check equality that will use matchers:


public boolean checkEquality(Object expected, Object received) {
String result = checkEqualityAndReturnError(expected, received);
return result == null || result.trim().isEmpty();
}

public String checkEqualityAndReturnError(Object expected, Object received) {
if (isIgnoreObject(expected)) {
return null;
}
if (expected == null && received == null) {
return null;
}
if (expected == null || received == null) {
return "Expected or received is null and the other is not: expected " + expected + " received " + received;
}
RootMatcher matcher = getMatcher(expected);
boolean result = matcher.matches(received);
if (result) {
return null;
} else {
StringBuilder sb = new StringBuilder();
matcher.describeTo(sb);
return sb.toString();
}
}

But how about collections (issue #4). To solve that, all you have to do is to implement matchers for collections that extend RootMatcher.

So the only remaining issue is #5: to make the matcher more flexible, to be able to tell the matcher which field should it ignore and which should it take into account. For this I introduced the concept of “ignoreObject”. This is an object that the matcher will ignore when it will find a reference to it in a template (expected object). How does it work? First of all, in RootMatcher I offer methods to return the ignore object for any Java type:


private final static Map ignorable = new HashMap();

static {
ignorable.put(String.class, "%%%%IGNORE_ME%%%%");
ignorable.put(Integer.class, new Integer(Integer.MAX_VALUE - 1));
ignorable.put(Long.class, new Long(Long.MAX_VALUE - 1));
ignorable.put(Float.class, new Float(Float.MAX_VALUE - 1));
}

/**
* we will ignore mock objects in matchers
*/
private boolean isIgnoreObject(Object object) {
if (object == null) {
return false;
}
Object ignObject = ignorable.get(object.getClass());
if (ignObject != null) {
return ignObject.equals(object);
}
return Mockito.mockingDetails(object).isMock();
}

@SuppressWarnings("unchecked")
public static M getIgnoreObject(Class clazz) {
Object obj = ignorable.get(clazz);
if (obj != null) {
return (M) obj;
}
return (M) Mockito.mock(clazz);
}

@SuppressWarnings("unchecked")
public static M getIgnoreObject(Object obj) {
return (M) getIgnoreObject(obj.getClass());
}

As you can see, the ignored object will be the one which is mocked. But for classes that cannot be mocked (final classes) I provided some arbitrary fixed values that are very improbable to appear(this part can be improved J). For this to work the developer has to use the equality methods provided in RootMatcher: checkEqualityAndReturnError which will check for ignored objects. Using this strategy and the builder pattern which I presented last year (http://www.javaadvent.com/2012/12/using-builder-pattern-in-junit-tests.html) I can easily make my assertions for a complex object:


import static […]RootMatcher.is;
Order expected = OrderBuilder.anOrder().withQuantity(2)
.withTimestamp(RootManager.getIgnoredObject(Long.class))
.withDescription(“specific description”).build()
assertThat(order, is(expected);

As you can see I could easily specify that the timestamp should be ignored and which allowed me to use the same matcher with a completely different set of fields to be verified.

Indeed, this strategy requires pretty much preparation, making all the builders and the matchers. But if we want to have a code that is tested, if we want to make testing a job that has the primary focus on the test flow that should be covered, we need such a foundation and these tools that help us easily establish our precondition and build our expected state.

Of course that the implementation can be improved using annotation, but the core concepts still remain.

I hope this article helps you improve your testing style, and if there’s enough interest I will do my best to put the complete code on a public repository.

Thank you.

Meta: this post is part of the Java Advent Calendar and is licensed under the Creative Commons 3.0 Attribution license. If you like it, please spread the word by sharing, tweeting, FB, G+ and so on!

Author: gpanther

Exit mobile version