Map equality using Hamcrest
Solution 1
The shortest way I've come up with is two statements:
assertThat( affA.entrySet(), everyItem(isIn(affB.entrySet())));
assertThat( affB.entrySet(), everyItem(isIn(affA.entrySet())));
But you can probably also do:
assertThat(affA.entrySet(), equalTo(affB.entrySet()));
depending on the implementations of the maps, and sacrificing the clarity of the difference report: that would just tell you that there is a difference, while the statement above would also tell you which one.
UPDATE: actually there is one statement that works independently of the collection types:
assertThat(affA.entrySet(), both(everyItem(isIn(affB.entrySet()))).and(containsInAnyOrder(affB.entrySet())));
Solution 2
Sometimes Map.equals()
is enough. But sometimes you don't know the types of Map
s is returned by code under tests, so you don't know if .equals()
will properly compare that map of unknown type returned by code with map constructed by you. Or you don't want to bind your code with such tests.
Additionally, constructing a map separately to compare the result with it is IMHO not very elegant:
Map<MyKey, MyValue> actual = methodUnderTest();
Map<MyKey, MyValue> expected = new HashMap<MyKey, MyValue>();
expected.put(new MyKey(1), new MyValue(10));
expected.put(new MyKey(2), new MyValue(20));
expected.put(new MyKey(3), new MyValue(30));
assertThat(actual, equalTo(expected));
I prefer using machers:
import static org.hamcrest.Matchers.hasEntry;
Map<MyKey, MyValue> actual = methodUnderTest();
assertThat(actual, allOf(
hasSize(3), // make sure there are no extra key/value pairs in map
hasEntry(new MyKey(1), new MyValue(10)),
hasEntry(new MyKey(2), new MyValue(20)),
hasEntry(new MyKey(3), new MyValue(30))
));
I have to define hasSize()
myself:
public static <K, V> Matcher<Map<K, V>> hasSize(final int size) {
return new TypeSafeMatcher<Map<K, V>>() {
@Override
public boolean matchesSafely(Map<K, V> kvMap) {
return kvMap.size() == size;
}
@Override
public void describeTo(Description description) {
description.appendText(" has ").appendValue(size).appendText(" key/value pairs");
}
};
}
And there is another variant of hasEntry()
that takes matchers as parameters instead of exact values of key and value. This can be useful in case you need something other than equality testing of every key and value.
Solution 3
I favor using Guava ImmutableMap. They support Map.equals()
and are easy to construct. The only trick is to explicitly specify type parameters, since hamcrest will assume the ImmutableMap
type.
assertThat( actualValue,
Matchers.<Map<String, String>>equalTo( ImmutableMap.of(
"key1", "value",
"key2", "other-value"
) ) );
Solution 4
Another option available now is to use the Cirneco extension for Hamcrest. It has hasSameKeySet()
(as well as other matchers for Guava "collections").
According to your example, it will be:
assertThat(affA, hasSameKeySet(affB));
You can use the following dependency for a JDK7-based project:
<dependency>
<groupId>it.ozimov</groupId>
<artifactId>java7-hamcrest-matchers</artifactId>
<version>0.7.0</version>
</dependency>
or the following if you are using JDK8 or superior:
<dependency>
<groupId>it.ozimov</groupId>
<artifactId>java8-hamcrest-matchers</artifactId>
<version>0.7.0</version>
</dependency>
Solution 5
This works like a charm and doesn't require two assertions like the accepted answer.
assertThat( actualData.entrySet().toArray(),
arrayContainingInAnyOrder(expectedData.entrySet().toArray()) );
mo-seph
Updated on July 09, 2022Comments
-
mo-seph over 1 year
I'd like to use hamcrest to assert that two maps are equal, i.e. they have the same set of keys pointing to the same values.
My current best guess is:
assertThat( affA.entrySet(), hasItems( affB.entrySet() );
which gives:
The method
assertThat(T, Matcher<T>)
in the typeAssert
is not applicable for the arguments (Set<Map.Entry<Householdtypes,Double>>, Matcher<Iterable<Set<Map.Entry<Householdtypes,Double>>>>
)I've also looked into variations of
containsAll
, and some others provided by the hamcrest packages. Can anyone point me in the right direction? Or do I have to write a custom matcher? -
Guenther about 7 yearsThat doesn't answer the original question.
-
Eric over 6 yearsInstead of using your own
hasSize
method, you should use,org.hamcrest.collection.IsMapWithSize.aMapWithSize(Matcher<? super Integer>)
-
Hans-Peter Störr over 6 yearsWouldn't that just compare the keys but not the values? The question wanted to compare values, too.
-
JeanValjean over 6 years@Dr.Hans-PeterStörr No, the question shows that the entry sets are compared
-
Lucas Cimon over 6 years@Taras: the reporting is less helpful with
.equals()
-
Pieter De Bie over 4 yearsThis however removes the descriptive assertion failures, which is one of the main reason of using Hamcrest.
-
Moritz Ringler over 4 yearsI had to convert the second access to affB into an array:
.and(containsInAnyOrder(affB.entrySet().toArray())
-
gwcoderguy about 4 yearsincorporating both the missing
()
forentrySet
and the necessarytoArray()
this ends up being:assertThat(affA.entrySet(), both(everyItem(isIn(affB.entrySet()))).and(containsInAnyOrder(affB.entrySet().toArray())))