1. Overview
1.概述
In this tutorial, we’ll go through some of the different ways of asserting the presence of a nested map inside an outer map. Mostly, we’ll discuss the JUnit Jupiter API and the Hamcrest API.
在本教程中,我们将介绍一些断言外层地图中是否存在嵌套地图的不同方法。我们将主要讨论 JUnit Jupiter API 和 Hamcrest API。
2. Asserting by Using Jupiter API
2.使用 Jupiter API 断言
For this article, we’ll use Junit 5, and hence let’s look at the Maven dependency:
在本文中,我们将使用 Junit 5,因此让我们来看看 Maven 依赖关系:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
Let’s take as an example a Map object with an outer map and an inner map. The outer map has an address key with the inner map as the value. It also has a name key with the value John:
以地图对象为例,它有一个外层地图和一个内层地图。外层地图有一个 address 键,其值为内层地图的值。它还有一个name键,其值为John:。
{
"name":"John",
"address":{"city":"Chicago"}
}
With examples, we’ll assert the presence of a key-value pair in the inner map.
通过示例,我们将断言内部映射中是否存在键值对。
Let’s begin with the basic assertTrue() method from Jupiter API:
让我们从 Jupiter API 的基本 assertTrue() 方法开始:
@Test
void givenNestedMap_whenUseJupiterAssertTrueWithCasting_thenTest() {
Map<String, Object> innerMap = Map.of("city", "Chicago");
Map<String, Object> outerMap = Map.of("address", innerMap);
assertTrue(outerMap.containsKey("address")
&& ((Map<String, Object>)outerMap.get("address")).get("city").equals("Chicago"));
}
We’ve used boolean expressions to evaluate if the innerMap is present in the outerMap. Then, we checked if the inner map had a city key with the value Chicago.
我们使用布尔表达式来评估 innerMap 是否存在于 outerMap 中。然后,我们检查内层地图是否有一个值为 Chicago 的 city 键。
However, the readability is lost due to the typecasting used above to avoid the compilation error. Let’s try to fix it:
然而,由于上文为避免编译错误而使用了类型转换,因此失去了可读性。让我们试着解决这个问题:
@Test
void givenNestedMap_whenUseJupiterAssertTrueWithoutCasting_thenTest() {
Map<String, Object> innerMap = Map.of("city", "Chicago");
Map<String, Map<String, Object>> outerMap = Map.of("address", innerMap);
assertTrue(outerMap.containsKey("address") && outerMap.get("address").get("city").equals("Chicago"));
}
Now, we changed the way we declared the outer map earlier. We declared it as Map<String, Map<String, Object>> instead of Map<String, Object>. With this, we avoided typecasting and achieved a little more readable code.
现在,我们改变了之前声明外层地图的方式。我们将其声明为 Map<String, Map<String, Object>> 而不是 Map<String,Object>。这样,我们就避免了类型转换,代码的可读性也更高了。
But, if the test fails we’ll not know exactly which assertion failed. To resolve this, let’s bring in the method assertAll():
但是,如果测试失败,我们就不知道到底是哪个断言失败了。为了解决这个问题,让我们引入方法 assertAll():
@Test
void givenNestedMap_whenUseJupiterAssertAllAndAssertTrue_thenTest() {
Map<String, Object> innerMap = Map.of("city", "Chicago");
Map<String, Map<String, Object>> outerMap = Map.of("address", innerMap);
assertAll(
() -> assertTrue(outerMap.containsKey("address")),
() -> assertEquals(outerMap.get("address").get("city"), "Chicago")
);
}
We moved the boolean expressions to the methods assertTrue() and assertEquals() within assertAll(). Hence, now we’ll know the exact failure. Moreover, it has improved the readability as well.
我们将布尔表达式移到了 assertAll() 中的方法 assertTrue() 和 assertEquals() 中。因此,现在我们可以知道确切的失败。此外,它还提高了可读性。
3. Asserting by Using Hamcrest API
3.使用 Hamcrest API 断言
Hamcrest Library offers a very flexible framework for writing Junit tests with the help of Matchers. We’ll use its out-of-the-box Matchers and develop a custom Matcher using its framework to verify the presence of a key-value pair in a nested map.
Hamcrest Library 提供了一个非常灵活的框架,可在 Matchers 的帮助下编写 Junit 测试。我们将使用其开箱即用的 Matchers 并使用其框架开发一个自定义 Matcher 以验证嵌套 map 中是否存在键值对。
3.1. With Existing Matchers
3.1.使用现有匹配器
For using the Hamcrest library we’ll have to update the Maven dependencies in the pom.xml file:
要使用 Hamcrest 库,我们必须更新 pom.xml 文件中的 Maven 依赖项:
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
Before we jump into examples, let’s first understand the support for testing a Map in the Hamcrest library. Hamcrest supports the following Matchers which can be used with the method assertThat():
在跳转到示例之前,让我们首先了解 Hamcrest 库中对 Map 的测试支持。Hamcrest 支持以下 Matchers 方法assertThat() :
- hasEntry() – Creates a matcher for Maps matching when the examined Map contains at least one entry whose key equals the specified key and whose value equals the specified value
- hasKey() – Creates a matcher for Maps matching when the examined Map contains at least one key that satisfies the specified matcher
- hasValue() – Creates a matcher for Maps matching when the examined Map contains at least one value that satisfies the specified valueMatcher
Let’s start with a basic example just like in the earlier section but we’ll use the methods assertThat() and hasEntry():
让我们从一个基本示例开始,就像前面的章节一样,但我们将使用 assertThat() 和 hasEntry() 方法:
@Test
void givenNestedMap_whenUseHamcrestAssertThatWithCasting_thenTest() {
Map<String, Object> innerMap = Map.of("city", "Chicago");
Map<String, Object> outerMap = Map.of("address", innerMap);
assertThat((Map<String, Object>)outerMap.get("address"), hasEntry("city", "Chicago"));
}
Apart from the ugly typecasting, the test is considerably easier to read and follow. However, we missed checking whether the outer map has the key address before getting its value.
除了丑陋的类型转换之外,这个测试在阅读和理解上都要容易得多。但是,在获取外层 map 的值之前,我们忽略了检查外层 map 是否有键值 address。
Shouldn’t we try fixing the above test? Let’s use hasKey() and hasEntry() to assert the presence of the inner map:
难道我们不应该尝试修正上述测试吗?让我们使用 hasKey() 和 hasEntry() 来断言内部映射的存在:
@Test
void givenNestedMap_whenUseHamcrestAssertThat_thenTest() {
Map<String, Object> innerMap = Map.of("city", "Chicago");
Map<String, Map<String, Object>> outerMap = Map.of("address", innerMap);
assertAll(
() -> assertThat(outerMap, hasKey("address")),
() -> assertThat(outerMap.get("address"), hasEntry("city", "Chicago"))
);
}
Interestingly, we’ve combined assertAll() from the Jupiter library with the Hamcrest library to test the map. Also to remove the typecast we tweaked the definition of the variable outerMap.
有趣的是,我们将 Jupiter 库中的 assertAll() 与 Hamcrest 库结合起来测试地图。为了消除类型转换,我们还调整了变量 outerMap 的定义。
Let’s see another way of doing the same thing with just the Hamcrest library:
让我们看看用 Hamcrest 库做同样事情的另一种方法:
@Test
void givenNestedMapOfStringAndObject_whenUseHamcrestAssertThat_thenTest() {
Map<String, Object> innerMap = Map.of("city", "Chicago");
Map<String, Map<String, Object>> outerMap = Map.of("address", innerMap);
assertThat(outerMap, hasEntry(equalTo("address"), hasEntry("city", "Chicago")));
}
Surprisingly, we could nest the hasEntry() method. This was possible with the help of the equalTo() method. Without it, the method would compile but the assertion would fail.
令人惊讶的是,我们可以嵌套 hasEntry() 方法。这是借助 equalTo() 方法实现的。如果没有该方法,方法可以编译,但断言会失败。
3.2. With a Custom Matcher
3.2.使用自定义匹配器
So far, we tried existing methods to check the presence of the nested map. Let’s try to create a custom Matcher by extending the TypeSafeMatcher class in the Hamcrest library.
到目前为止,我们尝试了现有的方法来检查嵌套地图是否存在。让我们尝试通过扩展 Hamcrest 库中的 TypeSafeMatcher 类来创建自定义匹配器。
public class NestedMapMatcher<K, V> extends TypeSafeMatcher<Map<K, Object>> {
private K key;
private V subMapValue;
public NestedMapMatcher(K key, V subMapValue) {
this.key = key;
this.subMapValue = subMapValue;
}
@Override
protected boolean matchesSafely(Map<K, Object> item) {
if (item.containsKey(key)) {
Object actualValue = item.get(key);
return subMapValue.equals(actualValue);
}
return false;
}
@Override
public void describeTo(Description description) {
description.appendText("a map containing key ").appendValue(key)
.appendText(" with value ").appendValue(subMapValue);
}
public static <K, V> Matcher<V> hasNestedMapEntry(K key, V expectedValue) {
return new NestedMapMatcher(key, expectedValue);
}
}
We had to override the matchSafely() method for checking the nested map.
我们必须重载 matchSafely() 方法来检查嵌套地图。
Let’s see how we can use it:
让我们看看如何使用它:
@Test
void givenNestedMapOfStringAndObject_whenUseHamcrestAssertThatAndCustomMatcher_thenTest() {
Map<String, Object> innerMap = Map.of
(
"city", "Chicago",
"zip", "10005"
);
Map<String, Map<String, Object>> outerMap = Map.of("address", innerMap);
assertThat(outerMap, hasNestedMapEntry("address", innerMap));
}
Visibly, the expression to check the nested map in the method assertThat() is far more simplified. We just had to call the hasNestedMapEntry() method to check the innerMap. Additionally, it compares the whole inner map, unlike the earlier checks where only one entry was checked.
显而易见,在方法 assertThat() 中检查嵌套地图的表达式要简化得多。我们只需调用 hasNestedMapEntry() 方法来检查 innerMap 即可。此外,它还会比较整个内层图,而不像之前的检查只检查一个条目。
Interestingly the custom Matcher works even if we define the outer map as Map(String, Object). We weren’t required to do any typecasting as well:
有趣的是,即使我们将外部映射定义为 Map(String, Object) ,自定义 Matcher 也能正常工作。我们也无需进行任何类型转换:
@Test
void givenOuterMapOfStringAndObjectAndInnerMap_whenUseHamcrestAssertThatAndCustomMatcher_thenTest() {
Map<String, Object> innerMap = Map.of
(
"city", "Chicago",
"zip", "10005"
);
Map<String, Object> outerMap = Map.of("address", innerMap);
assertThat(outerMap, hasNestedMapEntry("address", innerMap));
}
4. Conclusion
4.结论
In this article, we discussed the different ways to test the presence of a key-value in an inner nested map. We explored the Jupiter as well as the Hamcrest APIs.
在本文中,我们讨论了测试内部嵌套映射中是否存在键值的不同方法。我们探讨了 Jupiter 和 Hamcrest API。
Hamcrest provides some excellent out-of-the-box methods to support the assertions on nested maps. This prevents the use of boiler-plate code and hence helps in writing the tests in a more declarative way. Still, we had to write a custom Matcher to make the assertion more intuitive and support asserting multiple entries in the nested map.
Hamcrest 提供了一些出色的开箱即用方法来支持嵌套映射上的断言。这避免了使用模板代码,因此有助于以更声明的方式编写测试。不过,我们仍然需要编写一个自定义 Matcher 来使断言更加直观,并支持在嵌套映射中断言多个条目。
As usual, the code used in this article can be found over on GitHub.