How to assert Map contains Map with entry

35,432

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"));
    }
}
Share:
35,432
Jeff E
Author by

Jeff E

Updated on July 09, 2022

Comments

  • Jeff E
    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 is hasEntry(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
    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
    Jeff E over 8 years
    My 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
    Jeff E over 8 years
    My 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
    Jeff E over 8 years
    Thanks, this answer works. However, I think I would like to see if I can extend this to allow any level of nesting.
  • Florian Schaetz
    Florian Schaetz over 8 years
    Sounds 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 example containsValueInSubMap( 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).