Issue
I'm trying to use hamcrest matchers to match a list of objects against a list/array of their properties. For one property value this is not a problem, because I can do something like this:
assertThat(savedGroup.getMembers(),
containsInAnyOrder(hasProperty("name", is(NAMES[0]))));
For multiple property values I can use multiple hasProperty() calls
assertThat(savedGroup.getMembers(),
containsInAnyOrder(
hasProperty("name", is(NAMES[0])),
hasProperty("name", is(NAMES[1]))));
But is there a generic way to match against all values in the NAMES array?
Solution
The best way (IMO) to do this would be to combine the overloaded containsInAnyOrder
Matcher along with a custom FeatureMatcher
. Ultimately your code would look like this:
String[] expectedNames = new String[] { "John", "Bob", "Carol"};
assertThat(savedGroup.getMembers(), hasNames(expectedNames));
hasNames
is implemented as follows:
private Matcher<Iterable<? extends Member>> hasNames(String[] expectedNames) {
return containsInAnyOrder(Arrays.stream(expectedNames).map(name -> name(name)).collect(Collectors.toList()));
}
And the final part is the call to name
which generates a Matcher that will extract a property in a type-safe way from your object:
private Matcher<Member> name(String name) {
return new FeatureMatcher<Member, String>(equalTo(name), "name", "name") {
@Override
protected String featureValueOf(Member actual) {
return actual.getName();
}
};
}
The benefit of doing it this is way is that:
- You get the benefit of type-safety instead of using
hasProperty
- Your test now describes what you actual want to match on, i.e.
hasNames
- The code produced is now more flexible and composable. Want to match a single objects name? All you now need to do is
assertThat(member, has(name("Fred")))
You can get even more composability by moving the equalTo
sub-matcher to be part of the hasNames call like this:
private Matcher<Iterable<? extends Member>> hasNames(String[] expectedNames) {
return containsInAnyOrder(Arrays.stream(expectedNames).map(name -> name(equalTo(name))).collect(Collectors.toList()));
}
private Matcher<Member> name(Matcher<String> nameMatcher) {
return new FeatureMatcher<Member, String>(nameMatcher, "name", "name") {
@Override
protected String featureValueOf(Member actual) {
return actual.getName();
}
};
}
Answered By - tddmonkey
Answer Checked By - Clifford M. (JavaFixing Volunteer)