Saturday, April 26, 2014

Hamcrest Matching with Lambdas

Using Hamcrest matchers within your JUnit tests is a great way to create understandable, fluent tests.  You can make this test matching special by creating your own matchers specific to the objects you are testing.

In this blog post I will show you how to author custom Hamcrest matchers.  I'll also show how lambdas can make these custom matchers simpler to code and a bit more expressive.

Set-up

I'm using Java 8 in my example.  You will also need to include the appropriate Junit and Hamcrest jars.  The normal Junit jars come with a small portion of the Hamcrest matchers included as a dependency.  I suggest using the Junit jar without Hamcrest and including the full core Hamcrest as a seperate dependency.

My build.gradle file, notice the junit and hamcrest dependencies.

apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
    testCompile 'junit:junit-dep:4.+'
    testCompile 'org.hamcrest:hamcrest-core:1.+'
}

In my example we are killing zombies, my Zombie class is here.

Custom Hamcrest Matcher

There are a couple of abstract classes you can use from Hamcrest to build your custom matcher.  I'm using TypeSafeMatcher<T>, the generic is the class for which you are creating the matcher.  This method creates a custom matcher that asserts the type of zombie.

Hamcrest ensures your target object is the correct type and is not null.  The remaining methods to create are:
  1. protected boolean matchesSafely(Zombie zombie)
    This is where you do the actual matching to ensure the actual result matches expected.
  2. public void describeTo(Description description)
    These methods are used to build a failure message (see in red below).  This method creates the first line - "Expected: <description>"
  3. protected void describeMismatchSafely(Zombie zombie,    Description description
    This method creates the second line - "but: <description>"
java.lang.AssertionError: 
Expected: Zombie should be ABOMINATION
     but: was WALKER

Test Class using Custom Matcher

Custom Hamcrest Matcher using Lambdas

All good stuff and leads to expressive tests.  However, all the boiler-plate code around the custom matcher is tedious.  This is where lambdas can be used to simplify the custom matcher.

First: create a generic matcher that accepts lambdas.

This class is a template that can be used in multiple custom matchers.  It allows you to use lambda expressions in place of the custom matcher methods.

matchesSafely becomes a Predicate function
describeTo becomes a Consumer function
describeMismatchSafely becomes a BiConsumer (two argument consumer)

Using this class you can instantiate custom matchers with much less code.

Revised Zombie matcher using lambdas and the templates matcher class.

This class implements three custom Zombie matchers in the same space as the original version used to implement one custom matcher.


1 comment:

  1. Very cool and just what I was looking for. I'm actually surprised that this isn't built-in to hamcrest. Thanks for sharing

    ReplyDelete