Map equality using Hamcrest
Asked Answered
S

9

56

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 type Assert 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?

Saleme answered 24/3, 2010 at 16:8 Comment(3)
I have also tried containsAll et al. some time ago and it didn't seem to work - apparently hamcrest is a bit unreliable yet :-(Filide
Is there a reason why you can't use the .equals() of the Map implementation?Tannenwald
Ah - I hadn't realised that collections do proper .equals() comparisons. Has it always been like that? That makes life much easier! Thank you!Saleme
M
62

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().toArray())));
Microbalance answered 27/8, 2012 at 15:45 Comment(3)
@Taras: the reporting is less helpful with .equals()Organotherapy
I had to convert the second access to affB into an array: .and(containsInAnyOrder(affB.entrySet().toArray())Wheatear
incorporating both the missing () for entrySet and the necessary toArray() this ends up being: assertThat(affA.entrySet(), both(everyItem(isIn(affB.entrySet()))).and(containsInAnyOrder(affB.entrySet().toArray())))Delphiadelphic
S
41

Sometimes Map.equals() is enough. But sometimes you don't know the types of Maps 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.

Stephenstephenie answered 19/11, 2011 at 19:50 Comment(1)
Instead of using your own hasSize method, you should use, org.hamcrest.collection.IsMapWithSize.aMapWithSize(Matcher<? super Integer>)Bogosian
F
4

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"
) ) );
Fragrant answered 16/4, 2015 at 16:10 Comment(0)
G
4

This works like a charm and doesn't require two assertions like the accepted answer.

assertThat( actualData.entrySet().toArray(), 
    arrayContainingInAnyOrder(expectedData.entrySet().toArray()) );
Global answered 20/6, 2017 at 17:13 Comment(0)
L
2

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>
Loftin answered 30/12, 2015 at 14:17 Comment(2)
Wouldn't that just compare the keys but not the values? The question wanted to compare values, too.Nonobedience
@Dr.Hans-PeterStörr No, the question shows that the entry sets are comparedLoftin
I
0

Hamcrest now has a Matcher for size collection.

org.hamcrest.collection.IsCollectionWithSize

Inconvenience answered 10/9, 2016 at 6:51 Comment(1)
That doesn't answer the original question.Absher
S
0

If you need to compare a set of results with expectations and if you choose to use assertj library, you can do this:

// put set of expected values by your test keys
Map<K, V> expectations = ...;

// for each test key get result
Map<K, V> results = expectations.keySet().stream().collect(toMap(k -> k, k -> getYourProductionResult(k)));

assertThat(results).containsAllEntriesOf(expectations);

Note that containsAllEntriesOf does not compare maps for equality. If your production code returns actually a Map<K, V> you may want to add a check for keys assertThat(results).containsOnlyKeys((K[]) expectations.keySet().toArray());

Seoul answered 28/8, 2017 at 14:0 Comment(0)
P
0

I came to this thread testing with groovy and org.hamcrest:hamcrest:2.2

the method isIn is deprecated and the advice is to use is(in(...)) instead.

However in is a keyword in groovy!

So I ended up aliasing the import to do:

import static org.hamcrest.Matchers.*
import static org.hamcrest.Matchers.in as matchIn
....
....
@Test
void myTestMethod() {
    Map expectedSubMap = [
            one: "One",
            three: "Three"
            ]
    Map result = getMapToTest()
    assertThat(expectedSubMap.entrySet(), everyItem(is(matchIn(result.entrySet()))))
}
Peseta answered 11/6, 2021 at 11:59 Comment(0)
K
-1

A quite simple way is to use a utility method from Guava's com.google.common.collect.Maps class.

assertThat(Maps.difference(map1,map2).areEqual(),is(true));
Kroo answered 3/5, 2018 at 6:43 Comment(1)
This however removes the descriptive assertion failures, which is one of the main reason of using Hamcrest.Jp

© 2022 - 2024 — McMap. All rights reserved.