Matching made easy - Hamcrest

Hamcrest is a framework that enables the usage of “matchers”. Matchers, also known as specifications, are used to describe (business) requirements, on which objects can be checked.


boolean matches(Object candidate)



Because matchers evaluate into a boolean values, the logic of our matchers can easily be combined (specification pattern). By chaining matchers, we are capable of composing complex business logic, using our relatively simple matchers as building blocks.




boolean isFastRedCar = allOf(isFast(), isRed(), isCar()).matches(car);


Matchers can be used in many situations, these include:



  • Validating the status of an object

  • Selecting objects from a collection


Validation



We can use matchers to validate the status of an object. One logical application would be the writing of test-case assertions.



Image a service that retrieves every expired contract from the database. In order to test that service, we would assert that each returned contract has been expired.



contract.expirationDate < currentDate



With hamcrest this goes as follows:



ContractReadService contracts = ...;
assertThat(contracts.allExpired(), everyItem(hasProperty("expirationDate", lessThan(currentDate))));


Which is allot more intuitive than:



ContractReadService contracts = ...;
for(Contract expiredContract : contracts.allExpired()) {
assertTrue(expiredContract.getExpirationDate() < currentDate);
}


By using matchers, our assertions become allot more intuitive. The assertion looks much like a standard english sentence, making the code easier to understand, even without a programming background. In addition, matchers from our domain can be reused for testing. Promoting code reuse and simplifying our test-oriented code base.



Besides the simplification of testing, matchers can also be used to implement core domain functionality.




public class Person {
...
public void move() {
if(tired().matches(this)) {
walk();
} else {
sprint();
}
}
...
}


Because the logic of “is tired” gets modelled as matcher implementation, and not as private method in person, it can easily be reused.



Selection



Besides validation, we can also use matchers to select objects from a collection. Imagine a collection of persons, but we only want to execute logic on the adult persons.




Collection persons = ...;

public Collection adultPersons() {
Collection adults = new ArrayList();
Matcher isAdult = hasProperty("age", greaterThanOrEqualTo(18));
for(Person person : persons) {
if(isAdult.matches(person)) {
adults.add(person);
}
}
return adults;
}

doSomethingFor(adultPersons());


The above code can be simplified, using some library for collection filtering. Hamcrest provides an extension library named “hamcrest-collection”, which provides filtering functionality out of the box.



Integration with mockito



Hamcrest is integrated in various frameworks, such as Mockito. Mockito uses matchers to define method-call expectations. Which allows us to define expectations in a very expressive way.



Mockito API

No comments:

Post a Comment