Better testability for comparision of complex Java objects
hamcrest-composites
is a collection of Hamcrest matchers for comparing complex Java objects with better testability.
They apply to what might be called “composite objects”, i.e. iterable containers (in all their various forms) and objects whose properties define a nested tree of object
references. For Java programmers, hamcrest-composites
leverages the power of Java functional interfaces to make assertions about composite objects
more thorough, easier to express, and easier to debug.
With standard Hamcrest, verifying that two objects are equals
is easy. But comparing the full tree of object property values is much more involved and not directly supported. Although such “deep matching” is needed for testing, it’s often impossible (and almost always wrong!) to implement it using equals
. Instead, hamcrest-composites
makes it much easier to implement deep matching using a “composite matcher”. Similarly, because standard Hamcrest has always been a bit weak for comparing collections and arrays, hamcrest-composites
adds more robust matchers for all types of iterable containers.
Consider the case of a Drawing
object that contains a collection ofShape
instances, each of which has complex properties, such as a Color
. Consider the tests for a system that manipulates Drawing
objects.
How would a test verify that a Drawing
produced by the system contains all of the expected content? With hamcrest-composites
, it can be
as simple as this:
Drawing expected = ...
Drawing produced = ...
// Compare the complete tree of properties using a DrawingMatcher that extends BaseCompositeMatcher.
assertThat( produced, matches( new DrawingMatcher( expected)));
And defining a composite matcher for Drawing
instances can be as simple as this:
/**
* A composite matcher for Drawing instances.
*/
public class DrawingMatcher extends BaseCompositeMatcher<Drawing>
{
public DrawingMatcher( Drawing expected)
{
super( expected);
// Compare values for a simple scalar property.
expectThat( valueOf( "name", Drawing::getName).matches( Matchers::equalTo));
// Compare values for an Iterable container property, comparing the complete tree of properties for each member.
expectThat( valueOf( "elements", Drawing::getElements).matches( containsMembersMatching( ShapeMatcher::new)));
// Compare values for an array property.
expectThat( valueOf( "tags", Drawing::getTags).matches( Composites::containsElements));
}
}
But what if the composite match fails? For example, what if the produced Drawing
mostly matches, except that one of the shapes has the wrong color? Then you’d
see an assertion error message that pinpoints the discrepancy like this:
Expected: Drawing[Blues] matching elements=Iterable containing CIRCLE matching color=<Color[0,0,255]>
but: was <Color[255,0,0]>
Yes, hamcrest-composites
is based on standard Hamcrest 2.2. But compared to the standard Hamcrest, it offers several improvements.
The concept of “composite matcher” is new: There is nothing like it in standard Hamcrest. What’s new is a single Matcher class that will compare any two class instances
property-by-property. The problem is that every Matcher instance is bound to a specific expected value. But BaseCompositeMatcher
, together with the MatchesFunction
matcher, delays binding of property value matchers using “matcher supplier” functions.
ContainsMembers
vs. IsIterableContainingInAnyOrder
: Both of these matchers will verify that two Iterables contain the same set of members.
Likewise, ListsMembers
is similar to IsIterableContainingInOrder
. But:
ContainsMembers
and ListMembers
work even if either the expected or the matched Iterable is null
.ContainsMembers
and ListMembers
accept the members expected in multiple forms, using either an Iterable or an array or even an Iterator.ContainsMembers
and ListMembers
can optionally apply a member-specific composite matcher to perform a “deep match” on each individual member.ContainsMembers
and ListMembers
respond to a mismatch with a more concise and specific message, even in the case of deeply-nested collections.Matchers for arrays and Iterators: Sometimes collections come in different forms. hamcrest-composites
provides matchers equivalent to ContainsMembers
and ListMembers
that
can be used to verify the contents of arrays or Iterators.
Deep matching for Maps: Use the ContainsEntries
matcher to compare Map objects, comparing expected and matched map entries using specified key and value matchers.
To add composite matchers to an assertion…
Composites
class.To match all properties of an object…
BaseCompositeMatcher
. expectThat()
to add to the list of matchers applied to a matched object. valueOf()
to fluently define a MatchesFunction
matcher based on a property accessor. containsMembersMatching()
, etc. to fluently complete the matcher for a property of type Iterable, array, or Iterator. containsEntriesMatching()
, etc. to fluently complete the matcher for a property of type Map. To match all members of an iterable container, regardless of order…
ContainsMembers
matcher. ContainsElements
matcher. VisitsMembers
matcher. null
? No problem! To match all members of a sequence, in order…
ListsMembers
matcher. ListsElements
matcher. VisitsList
matcher. equals()
, use the ListsMatching
matcher. null
? No problem! To match Map entries…
ContainsEntries
matcher.null
? No problem! MapEntryMatcher
.MapEntryMatcher.Supplier
, specifying a value matcher and (optionally) a key matcher. The defaultequalTo
.For full details, see the complete Javadoc.
For more examples of how to use composite matchers, see the unit tests for: