Working with Spock we forget about a hidden gem – Hamcrest matchers. They are assertions turned into method objects. A matcher is usually parametrized with an expected value and is executed latel on the actual one.
When can we use it? For example in a table-driven specification with expected values in the table.
An expected value can be one of the following:
- exact value
- ‘not available’ text
- any number
How can we express these assertions without Hamcrest?
then: if (exactValue) { assert field == exactValue } else if (notAvailable) { assert field == messageSource.getMessage('na', null, LocaleContextHolder.locale) } else if (isNumber) { assert field.number } else { assert false, 'Wrong test configuration' } where: exactValue | notAvailable | isNumber '12345' | null | null null | true | null null | null | true
Bufff…
- there is a lot of boilerplate code
- the specification is misleading – what does it mean that expected
exactValue
isnull
? Is it reallynull
? Or simply this condition should not be checked?
How can we refactor this code using Hamcrest matchers?
then: that field, verifiedBy where: verifiedBy << [ org.hamcrest.CoreMatchers.equalTo('12345') notAvailable(), isNumber(), ] ... def notAvailable() { org.hamcrest.CoreMatchers.equalTo messageSource.getMessage('na', null, LocaleContextHolder.locale) } def isNumber() { [ matches: { actual -> actual.number }, describeTo: { Description description -> 'should be a number' } ] as BaseMatcher }
Result – shorter and more expressive feature method code.
You can find the full source code with mocked messageSource
in this gist. For more information about Hamcrest matchers, check Luke Daley’s article.