- The latest version (1.3.0) is now available at the Maven Central Repository.
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 of
Shape
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 theMatchesFunction
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 toIsIterableContainingInOrder
. But:ContainsMembers
andListMembers
work even if either the expected or the matched Iterable isnull
.ContainsMembers
andListMembers
accept the members expected in multiple forms, using either an Iterable or an array or even an Iterator.ContainsMembers
andListMembers
can optionally apply a member-specific composite matcher to perform a "deep match" on each individual member.ContainsMembers
andListMembers
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 toContainsMembers
andListMembers
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...
- Use the static methods defined by the
Composites
class.
- Use the static methods defined by the
-
To match all properties of an object...
- Create a subclass of
BaseCompositeMatcher
. - Use
expectThat()
to add to the list of matchers applied to a matched object. - Use
valueOf()
to fluently define aMatchesFunction
matcher based on a property accessor. - Use methods like
containsMembersMatching()
, etc. to fluently complete the matcher for a property of type Iterable, array, or Iterator. - Use
containsEntriesMatching()
, etc. to fluently complete the matcher for a property of type Map.
- Create a subclass of
-
To match all members of an iterable container, regardless of order...
- To match an Iterable, use the
ContainsMembers
matcher. - To match an array, use the
ContainsElements
matcher. - To match an Iterator, use the
VisitsMembers
matcher. - Even if the expected or matched container may be
null
? No problem! - And also compare individual members using a composite matcher? No problem!
- To match an Iterable, use the
-
To match all members of a sequence, in order...
- To match an Iterable, use the
ListsMembers
matcher. - To match an array, use the
ListsElements
matcher. - To match an Iterator, use the
VisitsList
matcher. - Without using
equals()
, use theListsMatching
matcher. - Even if the expected or matched sequence may be
null
? No problem! - And also compare individual members using a composite matcher? No problem!
- To match an Iterable, use the
-
To match Map entries...
- Use the
ContainsEntries
matcher. - Even if the expected or matched Map may be
null
? No problem! - Compare map entries using a
MapEntryMatcher
. You can easily construct one using aMapEntryMatcher.Supplier
, specifying a value matcher and (optionally) a key matcher. The default key matcher isequalTo
.
- Use the
For full details, see the complete Javadoc.
For more examples of how to use composite matchers, see the unit tests for: