07 March 2015
A Hamcrest Matcher for Lists of Lines
I have been writing a build plugin which writes a bunch of logging while it runs. I wanted to be able to take the output
from a test run (which is a List<String>
) and make assertions about what the output does or does not contain.
You may be sceptical about whether or not log output should be tested. In this case, the log output is basically the UI of the plugin and it is what people will use to troubleshoot and see what is going on. It’s important, and logging the correct information at the right time can be difficult, so yes! It should be unit tested.
I alway use Hamcrest for assertions due to the fact that the intention of the tests can be so clear when it is used well,
assertion error messages are clear, and the matchers are composable - more on compasability later. But I must admit I’ve
always hesitated to write my own custom matchers because they just seem a little bit too bulky with too much boiler plate.
That is until I discovered the TypeSafeDiagnosingMatcher<T>
base matcher that is built into Hamcrest. The TypeSafe
part
of the name means it uses generics; the Diagnosing
bit means it is easy to write error messages.
By extending this class you can not just quickly create a matcher, but crucially when matching fails the error message can be created at exactly the point you want it to be, i.e. in the match method. The two methods to override are:
As with normal Hamcrest matchers, you pass in expected values (or matchers) into the constructor, use describeTo
to
explain what you are trying to match, and then compare the expected value to the actual
value passed into the matchesSafely
method.
In my case, I wanted to be able to make assertions such as “from this list of Strings, only 1 contains the phrase ‘Completed successfully’”. From the test, it looks a bit like this:
I also had tests checking that certain strings are never printed, or printed exactly twice, etc. So my matcher needed to
know how many times something should occur, and what the matching criteria is. In the example above, I just wanted a line
that contained a string - but I could also want an exact match, or a string starting with a match, or a regular expression
match, or any other Hamcrest String matcher. This is what I meant above when I said Hamcrest is composable: I can use any
Matcher<String>
to compare lines. So the constructor of my matcher looks like this:
The static hasOneLineThat
method just takes a String matcher and passes that plus the value 1
to the constructor. In the matchesSafely
method I simply invoke the stringMatcher on each line of the output, and count how many times the matcher matches a line. If it
isn’t the number of expected occurences, then the error is written to the mismatchDiscriptor
. The full class should be easy enough to follow:
I complained about the bulkiness of matchers before - and this is 50 lines which does seem a lot - but it is all useful
code so I’m not so offended. Having the mismatchDescriptor
is the key thing here as it has made writing the reasons
for failing very simple.