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
exactValueisnull? 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.