Writing good unit tests, Part 2: Follow your nose

Assertions, exceptions, and refactoring in test code

1 2 3 4 5 Page 2
Page 2 of 5

Assertion matching with Hamcrest

In May 2005, Joe Walnes posted an article on his blog about using jMock constraints to improve assertion methods in JUnit. A simple JUnit assertion method with a constraint was an elegant replacement for all the other assertion methods typically cluttering test code:

Since using this one assert method, I've found my tests to be much easier to understand because of lack of noise, and I've spent a lot less time creating "yet another assertion" method for specific cases. And in most cases I never need to write a custom failure message, as the failures are self describing.

Walnes named his new assert method assertThat(); its syntax looked something like what you see in Listing 2:

Listing 2. assertThat() with flexible conditions

assertThat(x, is(3));
assertThat(x, is(not(4)));
assertThat(responseString, either(containsString("color")).or(containsString("colour")));
assertThat(myList, hasItem("3"));

The Hamcrest library, born of assertThat(), is a collection of matcher objects (also known as constraints or predicates) that allow match rules to be defined declaratively, and to be used in other frameworks.

Origins of Hamcrest

Hamcrest was originally spawned from the Constraints code in jMock1, which itself came from the Predicates code in DynaMock. The latest version of jMock (jMock2) uses Hamcrest directly.

You can use Hamcrest in testing frameworks, mocking libraries, or UI validation rules. But note that Hamcrest is not a testing library; it just happens that matchers are very useful for testing. You can include Hamcrest in any testing framework, but JUnit developers in particular have realized its potential, and it has been part of JUnit since version 4.4. Using Hamcrest matchers makes assert statements more readable and natural, and will also result in more informative, concrete default error messages (that is, the ones that you get without an explicit message parameter).

According to the Hamcrest tutorial, the most important Hamcrest matchers are as follows:

  • Core:
    • anything(): Always matches, useful if you don't care what the object under test is
    • describedAs(): Decorator to add custom failure description
    • is(): Decorator to improve readability
  • Logical:
    • allOf(): Matches if all matchers match, then short circuits (like Java &&)
    • anyOf(): Matches if any matchers match, then short circuits (like Java ||)
    • not(): Matches if the wrapped matcher doesn't match, and vice versa
  • Object:
    • equalTo(): Tests object equality using Object.equals()
    • hasToString(): Tests Object.toString()
    • instanceOf(), isCompatibleType(): Test type
    • notNullValue(), nullValue(): Test for null
    • sameInstance(): Tests object identity
  • Beans:
    • hasProperty(): Tests JavaBeans properties
  • Collections:
    • array(): Tests an array's elements against an array of matchers
    • hasEntry(), hasKey(), hasValue(): Test to see if a map contains an entry, key, or value
    • hasItem(), hasItems(): Test to see if a collection contains elements
    • hasItemInArray(): Tests to see if an array contains an element
  • Number:
    • closeTo(): Tests to see if floating point values are close to a given value
    • greaterThan(), greaterThanOrEqualTo(), lessThan(), lessThanOrEqualTo(): Test ordering
  • Text:
    • equalToIgnoringCase(): Tests string equality, ignoring case
    • equalToIgnoringWhiteSpace(): Tests string equality, ignoring differences in runs of whitespace
    • containsString(), endsWith(), startsWith(): Test string matching

Hamcrest also provides operators like is(), which act as syntactic sugar. This operator allows for elegant solutions like the one you see in Listing 3.

Listing 3. The Hamcrest is() operator adds some syntactic sugar

assertThat(color, equalTo("red"));
// or even better:
assertThat(color, is(equalTo("red")));
// optimal:
assertThat(color, is("red")); //allowed since 'is(T value)' is overloaded to return 'is(equalTo(value))'.

On its own, assertThat() has a powerful, de-cluttering effect on test code. You can also extend the library and even write custom matchers, such as the one I created for file-related checks. My custom matcher is shown in part in Listing 4, and you can find the complete code in this article's source code.

Listing 4. Extending Hamcrest for file-related checks

assertThat(generatedFile, exists());
assertThat(generatedFile, is(readable()));
assertThat(generatedFile, is(sized(equalTo(LOG4J_APPENDER_FILE_LENGTH))));
1 2 3 4 5 Page 2
Page 2 of 5