Ad-Hockery

ad-hockery: /ad·hok'@r·ee/, n.
Gratuitous assumptions... which lead to the appearance of semi-intelligent behavior but are in fact entirely arbitrary. Jargon File

Spock and Hamcrest

Groovy’s power assert and Hamcrest matchers both solve a similar problem – getting decent diagnostic information from an assertion failure. When writing JUnit or Spock tests in Groovy it’s typical to not use Hamcrest matchers as the power assert is so much simpler and just as effective. It’s worth bearing in mind, though that Hamcrest is also for helping clearly express the intent of an assertion. Spock provides support for Hamcrest matchers and I recently ran into a situation where I think it was the right thing to use.

If we’re testing a method that returns a Collection that may contain duplicate entries but whose order is either non-deterministic or irrelevant to the test what is the best way to make an assertion about the content of the collection?

Imagine we’re testing this interface:

interface Api {
  List<String> getThings()
}

The return value here is List but it could be a bag or some other collection type. It cannot be Set as the method may return a collection containing elements with a cardinality greater than 1.

Naïvely we might test it like this:

expect:
api.things == expected

where:
expected = ["a", "a", "b"]

If the order of the elements is non-deterministic this will sometimes pass and sometimes fail. No good.

Next we might try converting the result and the expectation to a Set so that the comparison does not take order into account:

expect:
api.things as Set == expected as Set

This will pass, but it can mask an error in the implementation because it’s not testing the cardinality of the elements. If getThings() returns ["a", "b"] or ["a", "a", "a", "a", "b"] the test will still pass which is almost certainly not what we intend.

Another route is to enforce an artificial ordering for the purposes of testing:

expect:
api.things.sort() == expected

This works so long as the element type is Comparable but I don’t find it very clear. As a reader of this code it might not be obvious why the sort is being done. The assertion is correct but at the cost of some clarity.

I tweeted about this and got the following suggestion:

expect:
def actual = api.things
actual.containsAll(expected) && actual.size() == expected.size()

This appears thorough but also masks an error. The assertion will pass if the the actual result is ["a", "a", "b"] or ["a", "b", "b"]. It’s checking the size of the list but not the cardinality of individual elements.

Rob Elliot pointed out that there’s a Hamcrest matcher for exactly this condition. Using that, I think yields the best result for clarity and correctness:

expect:
that api.things, containsInAnyOrder(*expected)

One of the huge advantages of Spock and Groovy’s power assert are in the expressiveness of test code but I think using Hamcrest in this case provides a more readable solution.

Implementation

To get this to work you need to import the hamcrest-all library that contains a lot more matchers than the hamcrest-core library that Spock pulls in by default.

build.gradle
testCompile "org.hamcrest:hamcrest-all:1.3"

Then you can static import Spock’s that method and the matcher itself:

import static spock.util.matcher.HamcrestSupport.that
import static org.hamcrest.Matchers.containsInAnyOrder
Web Statistics