修復 Mockito 中不明確的方法呼叫錯誤
1. 概述
在本教程中,我們將了解如何在Mockito框架的特定上下文中避免不明確的方法呼叫。
在Java中,方法重載允許一個類別有多個名稱相同但參數不同的方法。當編譯器無法根據提供的參數決定要呼叫的具體方法時,就會發生不明確的方法呼叫。
2. 介紹Mockito的ArgumentMatchers
Mockito 是用於對 Java 應用程式進行單元測試的模擬框架。該庫的最新版本可以在Maven Central Repository中找到。讓我們將依賴項加入pom.xml
:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
ArgumentMatchers
是 Mockito 框架的一部分:有了它們,我們可以在參數匹配給定條件時指定模擬方法的行為。
3. 重載方法定義
首先,我們定義一個方法,該方法採用Integer
作為參數,並且始終返回1
作為結果:
Integer myMethod(Integer i) {
return 1;
}
為了演示,我們希望重載方法使用自訂類型。因此,讓我們定義這個虛擬類別:
class MyOwnType {}
我們現在可以新增一個重載的myMethod()
,它接受MyOwnType
物件作為參數,並且始終傳回baeldung
作為結果:
String myMethod(MyOwnType myOwnType) {
return "baeldung";
}
直覺上,如果我們將null
參數傳遞給myMethod()
,編譯器將不知道應該使用哪個版本。此外,我們可以注意到該方法的傳回類型對此問題沒有影響。
4. 使用isNull()
進行模糊調用
讓我們天真地嘗試使用基本isNull()
ArgumentMatcher
來模擬對帶有 null 參數的myMethod()
的呼叫:
@Test
void givenMockedMyMethod_whenMyMethod_ThenMockedResult(@Mock MyClass myClass) {
when(myClass.myMethod(isNull())).thenReturn(1);
}
鑑於我們將定義myMethod()
的類別稱為MyClass
,我們透過測試的方法參數很好地註入了一個模擬的MyClass
物件。我們還可以注意到,我們還沒有在測試中添加任何斷言。讓我們運行這段程式碼:
java.lang.Error: Unresolved compilation problem:
The method myMethod(Integer) is ambiguous for the type MyClass
正如我們所看到的,編譯器無法決定使用myMethod()
的哪個版本,從而引發錯誤。讓我們強調一下,編譯器的決定僅基於方法參數。由於我們在指令中編寫了thenReturn(1)
,作為讀者,我們可以猜測其意圖是使用傳回Integer
的myMethod()
版本。但是,編譯器不會在其決策過程中使用這部分指令。
為了解決這個問題,我們需要使用重載的isNull()
ArgumentMatcher
,它以類別作為參數。例如,要告訴編譯器應該使用以Integer
作為參數的版本,我們可以寫:
@Test
void givenMockedMyMethod_whenMyMethod_ThenMockedResult(@Mock MyClass myClass) {
when(myClass.myMethod(isNull(Integer.class))).thenReturn(1);
assertEquals(1, myClass.myMethod((Integer) null));
}
我們添加了一個斷言來完成測試,現在它運行成功了。同樣,我們可以修改我們的測試以使用該方法的其他版本:
@Test
void givenCorrectlyMockedNullMatcher_whenMyMethod_ThenMockedResult(@Mock MyClass myClass) {
when(myClass.myMethod(isNull(MyOwnType.class))).thenReturn("baeldung");
assertEquals("baeldung", myClass.myMethod((MyOwnType) null));
}
最後,我們需要注意的是,我們還需要在斷言中呼叫myMethod()
時給出 null 類型。否則,這會因為同樣的原因而拋出!
5. 使用any()
進行不明確的調用
以同樣的方式,我們可以嘗試使用any()
ArgumentMatcher
來模擬接受任何參數的myMethod()
呼叫:
@Test
void givenMockedMyMethod_whenMyMethod_ThenMockedResult(@Mock MyClass myClass) {
when(myClass.myMethod(any())).thenReturn(1);
}
再次運行此程式碼會導致不明確的方法呼叫錯誤。我們在上一個案例中所做的所有評論在這裡仍然有效。特別是,編譯器甚至在查看thenReturn()
方法的參數之前就失敗了。
解決方案也類似:我們需要使用any()
ArgumentMatcher
的一個版本,它清楚地說明了預期參數的類型:
@Test
void givenMockedMyMethod_whenMyMethod_ThenMockedResult(@Mock MyClass myClass) {
when(myClass.myMethod(anyInt())).thenReturn(1);
assertEquals(1, myClass.myMethod(2));
}
大多數基本 Java 類型已經為此目的定義了 Mockito 方法。在我們的例子中, anyInt()
方法將接受任何Integer
參數。另一方面, myMethod()
的另一個版本接受我們自訂的MyOwnType
類型的參數。因此,我們需要使用any()
ArgumentMatcher
的重載版本,它將物件的類型作為參數:
@Test
void givenCorrectlyMockedNullMatcher_whenMyMethod_ThenMockedResult(@Mock MyClass myClass) {
when(myClass.myMethod(any(MyOwnType.class))).thenReturn("baeldung");
assertEquals("baeldung", myClass.myMethod((MyOwnType) null));
}
我們的測試現在運作正常:我們成功消除了不明確的方法呼叫錯誤!
六,結論
在本文中,我們了解了為什麼使用 Mockito 框架會面臨不明確的方法呼叫錯誤。此外,我們也展示了該問題的解決方案。在現實生活中,當我們有帶有大量參數的重載方法時,最有可能出現這種問題,並且我們決定使用約束較少的isNull()
或any()
ArgumentMatcher
,因為某些參數的值是與我們的測試無關。在簡單的情況下,大多數現代 IDE 甚至可以在我們需要執行測試之前指出問題。
與往常一樣,程式碼可以在 GitHub 上取得。