Home / Agile / Create your own hamcrest matcher

Create your own hamcrest matcher

If you are familiar with hamcrest and JUnit the time will come when you have
the need to create your own matchers.
Creating your own matcher can be as simple as useful. One reason for
creating your own matcher could be that your object is not a default object
like a String or a Collection. And if you would like to get a more readable
version of the assert for the next developer who has to read your
test. Let’s make an example. If you have an object with two methods;
getName” and “getNumber” and you would like to check whether the resulting object
has the correct values.
In my opinion the best way to verify this is to create two matchers and
combine them. Before we can combine these two matchers let’s see how to
create them.
For our examples we will test an instance of the following class:

public class Foo {
   public String getName() {
      return "Foo";
   }

   public int getNumber() {
      return 41;
   }
}

If you have a look at the Matcher interface you will see some hints
which tell you not to implement the Matcher itself. So have a look at
BaseMatcher which is referred to in the Matcher interface.

Let’s check if the number is 42 and the name is “Bar”. The samples show
possible solutions without every time creating its own class files. I just
write a small factory method instead of creating a matcher class. This
will be the first step while coding your test/matcher. If you see the
matcher can be used somewhere else, it should be very easy to create an
own class. It would also be helpful to create a factory class which
will centralize your matchers.

Using BaseMatcher

If we use BaseMatcher we will recognize that we have to implement the
following two methods:

  • public boolean matches(Object item) : here we will do the check.
    The item will be our object under test (Should be an instance of
    Foo).
  • public void describeTo(Description description) : here we will
    have to return a description of our matcher. This will simplify our
    life if there are failures.

Now let’s see how the implementation of a BaseMatcher could look like for
checking the value of getNumber().

private Matcher<Foo> hasNumber(final int i) {
   return new BaseMatcher<Foo>() {
      @Override
      public boolean matches(final Object item) {
         final Foo foo = (Foo) item;
         return i == foo.getNumber();
      }
      @Override
      public void describeTo(final Description description) {
         description.appendText("getNumber should return ").appendValue(i);
      }
   };
}

And the usage would look like this:

@Test
public void numberIs42() {
   final Foo testee = new Foo();
   assertThat(testee, hasNumber(42));
}

This results in a failing test. All right we want a failing test
otherwise we could not have a look at the features of our new matcher
since this will only be apparent while the test is failing ;)
So the test will give us this output:

java.lang.AssertionError: Expected: (getNumber should return <42>) got: <ch.adrianelsener.MatcherSampleTest$Foo@136070f0> 

You will agree with me that this is not very useful. But it is a good
demonstration why to do a failing test first, to demonstrate what can happen when
the test fails, otherwise you might never have noticed that there is room for
improvement.
There are two possible solutions to get a more powerful message:

  • Implementing toString on Foo would be one of them but not what we want.
  • Implement the method public void describeMismatch(final
    Object item, final Description description)
    in our
    matcher

If we add “describeMismatch”, our matcher would now look like this:

private Matcher<Foo> hasNumber(final int i) {
   return new BaseMatcher<Foo>() {
      @Override
      public boolean matches(final Object item) {
         final Foo foo = (Foo) item;
         return i == foo.getNumber();
      }
      @Override
      public void describeTo(final Description description) {
         description.appendText("getNumber should return ").appendValue(i);
      }
      @Override
      public void describeMismatch(final Object item, final
Description description) {
         description.appendText("was").appendValue(((Foo) item).getNumber());
     }
   };
}

Now the result of the failing test tells us:

Expected: getNumber should return <42> but: was<41> 

This will work great, but you will have to do casts all the time even hough you
define the type of the object to test in the signature. To
solve this you can use the TypsafeMatcher.

TypesafeMatcher

This matcher is almost identical to BaseMatcher. Almost ;) The
differences are:

  • protected boolean matchesSafely(final T item)
  • protected void describeMismatchSafely(final T item, final
    Description mismatchDescription)

Now the item already is of the known type and it is verified by
TypesafeMatcher to be what it should be. If you use the assertThat
method it would change nothing, since there are generics to define
what you can match and what not but the casts are no longer necessary.
Our sample matcher will change into something like this:

private Matcher<Foo> hasNumber(final int i) {
   return new TypeSafeMatcher<Foo>() {
      @Override
      public void describeTo(final Description description) {
         description.appendText("getNumber should return ").appendValue(i);
      }

      @Override
      protected void describeMismatchSafely(final Foo item, final
Description mismatchDescription) {
         mismatchDescription.appendText(" was ").appendValue(item.getNumber());
      }

      @Override
      protected boolean matchesSafely(final Foo item) {
         return i == item.getNumber();
      }
   };
}

If you let the test run you will see the same result as with the
matcher extended from BaseMatcher. But we don’t have to cast the
object all the time. Now you’ll ask what will the
TypesafeDiagnosingMatcher bring more. Let’s have a look.

TypesafeDiagnosingMatcher

With the TypesafeDiagnosingMatcher you only have to implement two
methods. These are:

  • public void describeTo(final Description description) : This
    has still the same function: Describe what the matcher is doing
  • protected boolean matchesSafely(final T item, final Description
    mismatchDescription) : Here we do the check AND the
    error description.

So we change the creation of our matcher to the following:

private Matcher<Foo> hasNumber(final int i) {
   return new TypeSafeDiagnosingMatcher<Foo>() {
      @Override
      public void describeTo(final Description description) {
         description.appendText("getNumber should return ").appendValue(i);
      }

      @Override
      protected boolean matchesSafely(final Foo item, final
Description mismatchDescription) {
         mismatchDescription.appendText(" was ").appendValue(item.getNumber());
         return i == item.getNumber();
      }
   };
}

There is one point you have to keep in mind. If the match fails, the method
matchesSafely will be called at least twice. This is because the
method will be called to do the match AND to write
the failure message.

FeatureMatcher

There is one abstract matcher which will be very easy to use for our
problem. When implementing FeatureMatcher we just have to implement
protected abstract U featureValueOf(T actual);” (T will be our item
and U the type that has to be checked) and all the rest will be done
by FeatureMatcher. No more failuretext no more description. Nice,
isn’t it?
Here how our matcher looks like after implementing it with FeatureMatcher:

private Matcher<Foo> hasNumberFeatureMatcher(final Integer i) {
   return new FeatureMatcher<Foo, Integer>(equalTo(i), "number", "number") {
      @Override
      protected Integer featureValueOf(final Foo actual) {
         return actual.getNumber();
      }
   };
}

After executing we will have this StackTrace:

java.lang.AssertionError: Expected: number <42> but: number was <41> 

As you see, we have to change something. The FeatureMatcher is
designed to return a value (feature) of an object. Because of this we
have something special in the constructor. Have you seen it? Yes,
the equalTo matcher. This is because the feature matcher is
designed to take an other matcher. So it would also be possible to
compare with lessThan/greaterThan etc. Nice isn’t it?

Combining two matchers

Now let’s do the rest. Our goal was to compare two getters. To do this
the CombinableMatcher will be helpful. Just have a look at Matchers
and you will find the following way to combine two matchers:
Matchers.both(MatcherA).and(MatcherB)”. In my opinion this is a very
readable way to combine two matchers. But there is one problem with
it: The message will not be as good as it should. If we let this run

assertThat(testee, both(hasName("Foo")).and(hasNumber(42)));

we will se something like this:

java.lang.AssertionError: Expected: (getName should return "Foo" and getNumber should return <42>) but: was <ch.adrianelsener.Foo@2484e723> 

if we do not implement toString on our result we can’t really get
enough information to get a conclusion what happend. Here I would do a
little manual work.

class MyCombinableMatcher<T> extends BaseMatcher<T> {
   private final List<Matcher<? super T> matchers = new ArrayList<>();
   private final List<Matcher<? super T> failed = new ArrayList<>();

   private MyCombinableMatcher(final Matcher matcher) {
      matchers.add(matcher);
   }

   public MyCombinableMatcher and(final Matcher matcher) {
      matchers.add(matcher);
      return this;
   }

   @Override
   public boolean matches(final Object item) {
      for (final Matcher<? super T> matcher : matchers) {
         if (!matcher.matches(item)) {
            failed.add(matcher);
            return false;
         }
      }
      return true;
   }

   @Override
   public void describeTo(final Description description) {
      description.appendList("(", " " + "and" + " ", ")", matchers);
   }

   @Override
   public void describeMismatch(final Object item, final Description
description) {
      for (final Matcher<? super T> matcher : failed) {
         description.appendDescriptionOf(matcher).appendText(" but ");
         matcher.describeMismatch(item, description);
      }
   }

   public static <LHS> MyCombinableMatcher<LHS> all(final Matcher<? super LHS> matcher) {
      return new MyCombinableMatcher<LHS>(matcher);
   }
}

If we now execute this line:

assertThat(testee, all(hasNameTypesafeMatcher("Bar")).and(hasNumber(42)));
java.lang.AssertionError: Expected: (getName should return "Foo" and getNumber should return <42>) but: getName should return "Foo" but was "Bar" getNumber should return <42> but was <41> 

assertThat or assertThat ?

If you see just something like this

java.lang.AssertionError: Expected: (getName should return "Foo" and getNumber should return <42>) got: <ch.adrianelsener.Foo@21a79b48> 

Then it might be because you have used the assertThat from JUnit and not the one
from hamcrest. This is because JUnit delivers an assertThat to get
small support for hamcrest. But it does not use all of the “new” features
of hamcrest. They deliver an old implementation. All you have to
do is use MatcherAssert.assertThat within the hamcrest library. It
would be better to get the JUnit jar without hamcrest and replace it
completely by the hamcrest jar itself.