How to assert Map contains Map with entry
Solution 1
I would probably extend a new Matcher for that, something like that (beware, NPEs lurking):
class SubMapMatcher extends BaseMatcher<Map<?,?>> {
private Object key;
private Object subMapKey;
private Object subMapValue;
public SubMapMatcher(Object key, Object subMapKey, Object subMapValue) {
super();
this.key = key;
this.subMapKey = subMapKey;
this.subMapValue = subMapValue;
}
@Override
public boolean matches(Object item) {
Map<?,?> map = (Map<?,?>)item;
if (!map.containsKey(key)) {
return false;
}
Object o = map.get(key);
if (!(o instanceof Map<?,?>)) {
return false;
}
Map<?,?> subMap = (Map<?,?>)o;
return subMap.containsKey(subMapKey) && subMap.get(subMapKey).equals(subMapValue);
}
@Override
public void describeTo(Description description) {
description.appendText(String.format("contains %s -> %s : %s", key, subMapKey, subMapValue));
}
public static SubMapMatcher containsSubMapWithKeyValue(String key, String subMapKey, String subMapValue) {
return new SubMapMatcher(key, subMapKey, subMapValue);
}
}
Solution 2
If you only want to put Map<String, Object>
as values in your outerMap
adjust the declaration accordingly. Then you can do
@Test
public void testMapHasMap() {
Map<String, Map<String, Object>> outerMap = new HashMap<>();
Map<String, Object> nestedMap = new HashMap<String, Object>();
nestedMap.put("foo", "bar");
outerMap.put("nested", nestedMap);
Object value = "bar";
assertThat(outerMap, hasEntry(equalTo("nested"), hasEntry("foo", value)));
}
Object value = "bar";
is necessary for compile reasons. Alternatively you could use
assertThat(outerMap,
hasEntry(equalTo("nested"), Matchers.<String, Object> hasEntry("foo", "bar")));
Solution 3
If You declare outerMap as Map<String, Map<String, Object>>
you don't need the ugly cast. Like this:
public class MapContainsMapTest {
@Test
public void testMapHasMap() {
Map<String, Map<String, Object>> outerMap = new HashMap<>();
Map<String, Object> nestedMap = new HashMap<>();
nestedMap.put("foo", "bar");
outerMap.put("nested", nestedMap);
assertThat(outerMap.get("nested"), hasEntry("foo", "bar"));
}
}
Jeff E
Updated on July 09, 2022Comments
-
Jeff E almost 2 years
I have a unit test that needs to check for a nested map value. I can get my assertion to work by pulling out the entry and matching the underlying
Map
, but I was looking for a clear way to show what the assertion is doing. Here is a very simplified test:import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasEntry; import java.util.HashMap; import java.util.Map; import org.junit.Test; public class MapContainsMapTest { @Test public void testMapHasMap() { Map<String, Object> outerMap = new HashMap<String, Object>(); Map<String, Object> nestedMap = new HashMap<String, Object>(); nestedMap.put("foo", "bar"); outerMap.put("nested", nestedMap); // works but murky assertThat((Map<String, Object>) outerMap.get("nested"), hasEntry("foo", "bar")); // fails but clear assertThat(outerMap, hasEntry("nested", hasEntry("foo", "bar"))); } }
It seems the problem is the outer map is being compared using
hasEntry(K key, V value)
while what I want to use ishasEntry(Matcher<? super K> keyMatcher, Matcher<? super V> valueMatcher)
. I am not sure how to coerce the assertion to use the second form.Thanks in advance.
-
David Pérez Cabrera over 8 years@DanGetz if I remove the cast, it doesnt compile, outerMap must be declared as Map<String, Map<String, Object>>.
-
Jeff E over 8 yearsMy example is a simplified version of my test in which the outerMap is a JSON object where not all the entries are maps, so I must keep the map declaration as written.
-
Jeff E over 8 yearsMy example is a simplified version of my test in which the outerMap is a JSON object where not all the entries are maps, so I must keep the map declaration as written.
-
Jeff E over 8 yearsThanks, this answer works. However, I think I would like to see if I can extend this to allow any level of nesting.
-
Florian Schaetz over 8 yearsSounds easy enough, you can simple use a signature like
Object value, String... keys
. Then iterate through the keys, get the submap until you reach last one (or stumble upon a null or not-map as your value) and check the value. For examplecontainsValueInSubMap( someValue, map0Key, map1Key, map2Key);
Of course, personally I would NOT suggest using a standard Map for a key -> "value OR submap" mapping, but instead something more specific which would also allow have generic value types (a map with submaps and values is effectively a tree, should be easy to find something).