DatumEdge The website of James Shaw

Idiomatic Hamcrest

7 April 2012

Hamcrest is a useful library that lets you build assertions declaratively. Having worked with the library for a while now, I've found a few useful patterns and a handful of gotchas when writing Hamcrest matchers.

Make matchers granular

Sometimes you want to compare features of two objects having the same type. Instead of making one matcher that compares many features, write many matchers, each of which compares one feature. By doing this, you can use your custom matchers with Hamcrest's built-in combining matchers. FeatureMatchers work well for this type of fine-grained matcher. If the objects's features are bean properties you can avoid writing a custom matcher by using Hamcrest's hasProperty().

Compose matchers

You get more power by allowing matchers to accept other matchers. Here's an example that combines Hamcrest matchers to make an assertion on a JAX-RS Response:

[Aside: Due to a bug in 1.3RC2, you will need to use a more recent version to get useful diagnostics from CombinableMatchers]

Prefer TypeSafeDiagnosingMatchers over TypeSafeMatchers

Unless your matchers are dealing with simple types such as Strings or primitives, TypeSafeDiagnosingMatchers will be able to produce better diagnostics. If you're seeing diagnostics such as Expected: status <404> but: was <com.sun.jersey.core.spi.factory.ResponseImpl@575b2f17>, you probably need a diagnosing matcher.

Avoid null checks inside Matcher code

TypeSafeMatcher and TypeSafeDiagnosingMatchers won't even call your matcher code if the actual value is null. If you're expecting a null value, use Matchers.nullValue() instead of a custom matcher.

Avoid using mutable objects as actual or expected items

Hamcrest assumes that Matcher.matches() is idempotent; indeed, TypeSafeDiagnosingMatcher.describeMismatch() gets called twice when two objects do not match. This means that you should avoid writing an InputStream matcher, for example.