使用 JUnit 斷言巢狀映射
1. 概述
在本教程中,我們將介紹斷言外部映射內巢狀映射的存在的一些不同方法。我們主要討論 JUnit Jupiter API 和 Hamcrest API。
2. 使用 Jupiter API 進行斷言
在本文中,我們將使用 Junit 5,因此讓我們看看Maven 依賴項:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
我們以具有外部映射和內部映射的 Map 物件為例。外部映射有一個address
鍵,內部映射作為值。它還具有一個值為 John 的name
鍵John:
{
"name":"John",
"address":{"city":"Chicago"}
}
透過範例,我們將斷言內部映射中存在鍵值對。
讓我們從 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"));
}
我們使用布林表達式來評估innerMap
是否存在於outerMap
中。然後,我們檢查內部地圖是否有一個值為Chicago
city
鍵。
然而,由於上面使用類型轉換來避免編譯錯誤,因此失去了可讀性。讓我們嘗試修復它:
@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"));
}
現在,我們改變了先前聲明外部地圖的方式。我們將其宣告為Map<String, Map<String, Object>>
而不是Map<String, Object>
。這樣,我們就避免了類型轉換並獲得了更易讀的程式碼。
但是,如果測試失敗,我們將無法確切知道哪個斷言失敗了。為了解決這個問題,我們引入方法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")
);
}
我們將布林表達式移至assertAll()
中的assertTrue()
和assertEquals()
方法。因此,現在我們將知道確切的失敗原因。此外,它還提高了可讀性。
3. 使用 Hamcrest API 進行斷言
Hamcrest 庫提供了一個非常靈活的框架,用於在Matchers
的幫助下編寫 Junit 測試。我們將使用其開箱即用的Matchers
,並使用其框架開發自訂Matcher
來驗證嵌套映射中是否存在鍵值對。
3.1.使用現有的匹配器
為了使用 Hamcrest 函式庫,我們必須更新pom.xml
檔案中的Maven 依賴項:
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
在我們開始範例之前,我們首先了解 Hamcrest 庫中對測試Map
的支援。 Hamcrest 支援下列Matchers
,可與方法assertThat()
一起使用:
-
hasEntry()
– 當檢查的Map
包含至少一個其鍵等於指定鍵且其值等於指定值的條目時,為 Maps 匹配建立一個匹配器 -
**hasKey()** –
當檢查的Map
至少包含一個滿足指定匹配器的鍵時,為 Maps 匹配創建一個匹配器 -
**hasValue()** –
當檢查的Map
至少包含一個滿足指定 valueMatcher 的值時,為 Maps 匹配建立一個匹配器
讓我們從一個基本範例開始,就像前面的部分一樣,但我們將使用方法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"));
}
除了醜陋的類型轉換之外,該測試更容易閱讀和遵循。然而,我們在獲取其值之前錯過了檢查外部映射是否具有關鍵address
。
我們不應該嘗試修復上述測試嗎?讓我們使用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"))
);
}
有趣的是,我們將 Jupiter 庫中的assertAll()
與 Hamcrest 庫結合起來測試地圖。另外,為了刪除型別轉換,我們調整了變數outerMap
的定義。
讓我們看看用 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")));
}
令人驚訝的是,我們可以嵌套hasEntry()
方法。這在equalTo()
方法的幫助下是可能的。如果沒有它,該方法將編譯,但斷言將失敗。
3.2.使用自訂匹配器
到目前為止,我們嘗試了現有的方法來檢查嵌套映射的存在。讓我們嘗試透過擴充 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);
}
}
我們必須重寫matchSafely()
方法來檢查巢狀映射。
讓我們看看如何使用它:
@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));
}
顯然, assertThat()
方法中檢查嵌套映射的表達式要簡化得多。我們只需呼叫hasNestedMapEntry()
方法來檢查innerMap
。此外,它還會比較整個內部映射,這與先前僅檢查一個條目的檢查不同。
有趣的是,即使我們將外部映射定義為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。結論
在本文中,我們討論了測試內部巢狀映射中鍵值是否存在的不同方法。我們探索了 Jupiter 以及 Hamcrest API。
Hamcrest 提供了一些優秀的開箱即用方法來支援巢狀映射上的斷言。這可以防止使用樣板程式碼,因此有助於以更具聲明性的方式編寫測試。儘管如此,我們仍然必須編寫一個自訂Matcher
以使斷言更加直觀並支援在巢狀映射中斷言多個條目。
與往常一樣,本文中使用的程式碼可以在 GitHub 上找到。