如何断言Map包含具有entry的Map

16

我有一个单元测试需要检查一个嵌套地图的值。 我可以通过取出该条目并匹配底层Map来使我的断言起作用,但我正在寻找一种清晰的方式来展示该断言正在执行的操作。以下是一个非常简化的测试:

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")));
    }
}
看起来问题出在使用了 hasEntry(K key, V value) 进行了外部 Map 的比较,但我想使用的是 hasEntry(Matcher<? super K> keyMatcher, Matcher<? super V> valueMatcher)。我不确定如何强制断言使用第二种形式。
提前感谢。

似乎问题在于使用hasEntry(is("key"), is("value"))比较外部映射表,例如会导致使用第二种形式。也许你可以使用equalTo匹配器代替is,但无论哪种方式,可读性都会大打折扣。 - mystarrocks
问题出在外部测试上,所以我需要类似 equalTo("nested")... 这样的东西,但是如果我不将外部映射类型更改为 Map<String, Map<String,Object>>,这是行不通的。 - Jeff E
4个回答

6
如果您只想在您的outerMap中将Map作为值,调整声明即可。然后您可以执行以下操作:
@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"; 是为了编译需要而必需的。或者您可以使用

assertThat(outerMap,
   hasEntry(equalTo("nested"), Matchers.<String, Object> hasEntry("foo", "bar")));

我的示例是我的测试的简化版本,其中outerMap是一个JSON对象,其中并非所有条目都是映射,因此我必须保持映射声明不变。 - Jeff E

5
如果您将outerMap声明为 Map<String, Map<String, Object>>,则不需要进行丑陋的强制类型转换。像这样:
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"));
    }
}

1
@DanGetz 如果我去掉强制转换,它就无法编译,outerMap必须声明为Map<String,Map<String,Object>>。 - David Pérez Cabrera
我的示例是我的测试的简化版本,其中outerMap是一个JSON对象,其中并非所有条目都是映射,因此必须保留映射声明的原样编写。 - Jeff E

4
我可能会为此编写一个新的Matcher,类似于以下内容(注意,可能存在空指针异常):

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);
}

}

谢谢,这个答案可行。不过,我想看看能否将其扩展以允许任何层次的嵌套。 - Jeff E
听起来很简单,您可以简单地使用这样的签名Object value, String... keys。然后遍历键,获取子图直到到达最后一个(或者遇到值为null或非映射的情况),并检查值。例如containsValueInSubMap(someValue, map0Key, map1Key, map2Key); 当然,个人不建议使用标准Map进行键->“值或子映射”的映射,而应该使用更具体的内容,这也可以允许具有通用值类型(带有子映射和值的映射实际上是一棵树,应该很容易找到)。 - Florian Schaetz

1

尝试这样做:

        assertThat(nestedMap).contains(Map.entry("foo", "bar"));
        assertThat(outerMap).contains(Map.entry("nested", nestedMap));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接