Hamcrest Matcher for Hamcrest Matchers

Everyone loves writing unit tests, don’t you? If your answer is “no” you should have a look into Hamcrest. Hamcrest, if applied properly, makes writing unit tests much easier and, yes, enjoyable.

This post is not an introduction into Hamcrest though. It’s about a topic which has not been covered often.

We at dmfs aim to create a Hamcrest Matcher for every new type we create so we can write tests quickly, easily and in a declarative manner.

The one thing we didn’t test with a Hamcrest Matcher yet were our Hamcrest Matchers themselves.

The tests for our Matchers usually look old fashioned like this (example from jems PresentMatcherTest.java)

@Test
public void testMatchesSafely() throws Exception
{
    assertThat(new PresentMatcher<>().matchesSafely(absent(), new Description.NullDescription()), is(false));
    assertThat(new PresentMatcher<String>().matchesSafely(new Present<>("test"), new Description.NullDescription()), is(true));
    assertThat(new PresentMatcher<>("test").matchesSafely(new Present<>("test"), new Description.NullDescription()), is(true));
    assertThat(new PresentMatcher<>("test").matchesSafely(new Present<>("tost"), new Description.NullDescription()), is(false));

    assertThat(present().matchesSafely(new Present<>("test"), new Description.NullDescription()), is(true));
    assertThat(present("test").matchesSafely(new Present<>("test"), new Description.NullDescription()), is(true));
    assertThat(present(is("test")).matchesSafely(new Present<>("test"), new Description.NullDescription()), is(true));

    assertThat(present("tost").matchesSafely(new Present<>("test"), new Description.NullDescription()), is(false));
    assertThat(present(is("tost")).matchesSafely(new Present<>("test"), new Description.NullDescription()), is(false));
}


@Test
public void testValueMismatchDescription() throws Exception
{
    Description mismatchMsg = new StringDescription();
    new PresentMatcher<>("123").describeMismatch(new Present<>("abc"), mismatchMsg);
    assertThat(mismatchMsg.toString(), is("present, but value was \"abc\""));
}


@Test
public void testPresenceMismatchDescription() throws Exception
{
    Description mismatchMsg = new StringDescription();
    new PresentMatcher<>("123").describeMismatch(absent(), mismatchMsg);
    assertThat(mismatchMsg.toString(), is("not present"));
}


@Test
public void testDescribeTo() throws Exception
{
    Description description = new StringDescription();
    new PresentMatcher<>("123").describeTo(description);
    assertThat(description, hasToString("present with value \"123\""));
}

Although these tests use assertThat and Matchers, this is not much different from testing with the good old assertEquals and assertTrue and completely misses the point of Hamcrest.

MatcherMatcher

Much to my surprise the Hamcrest library doesn’t seem to contain any Matcher to test Matchers, nor was I able to find anything like that.

That’s why, in jems 1.18, we’ve finally published Hamcrest Matchers to test other Hamcrest Matchers.

The class MatcherMatcher contains variations of the following three types of Matchers.

matches

takes an object of type <T> and checks whether the testee successfully matches this object. There is only one version of this which takes the matching object.

mismatches

takes an object of type <T> and checks whether the testee successfully mismatches this object. There are two more versions of this matcher which also test the resulting mismatch description. They take an additional String (matched with is(…)) or any other Matcher<String>.

describesAs

matches the description of a Matcher. It either takes an argument which is a Matcher to match the String value of the description or the actual String of the description.

Usage

Putting it all together we can re-write the test for PresentMatcher containing only three assertThat statements.

@Test
public void test() throws Exception
{
    assertThat(present(),
            AllOf.<Matcher<Optional<Object>>>allOf(
                    matches(new Present<>(new Object())), // matches any present value
                    mismatches(new Absent<>(), "not present"), // mismatches absent only
                    describesAs("present with value ANYTHING")
            )
    );

    assertThat(present("test"),
            AllOf.<Matcher<Optional<String>>>allOf(
                    matches(new Present<>("test")), // value must match exactly
                    mismatches(new Absent<>(), "not present"), // mismatches absent …
                    mismatches(new Present<>("abc"), "present, but value was \"abc\""), // … and wrong values
                    describesAs("present with value \"test\"")
            )
    );

    assertThat(present(is("test")),
            AllOf.<Matcher<Optional<String>>>allOf(
                    matches(new Present<>("test")), // value must match exactly
                    mismatches(new Absent<>(), "not present"), // mismatches absent …
                    mismatches(new Present<>("abc"), "present, but value was \"abc\""), // … and wrong values
                    describesAs("present with value is \"test\"")
            )
    );
}

This is a far better test than the version above. It’s more compact and it’s declarative.

It should be mentioned the class Present is deliberately not tested using PresentMatcher in order to allow its usage in this test without circular reasoning.

The tests for these new Matchers use MatcherMatchers as well, except for those methods which would result in circular reasoning, of course.

Finally there is no excuse anymore for not having your custom Hamcrest Matchers tested properly.