Mockito MockSettings概述
1.概述
通常,Mockito為我們的模擬對象提供的默認設置應該綽綽有餘。
但是,有時可能需要在模擬創建期間提供其他模擬設置。這在調試,處理遺留代碼或涵蓋一些極端情況時可能很有用。
在先前的教程中,我們學習瞭如何使用寬大的模擬。在本快速教程中,我們將學習如何使用MockSettings
界面提供的其他一些有用功能。
2.模擬設置
簡而言之, MockSettings
接口提供了Fluent API,使我們能夠在模擬創建過程中輕鬆添加和組合其他模擬設置。
創建模擬對象時,所有模擬都帶有一組默認設置。讓我們看一個簡單的模擬示例:
List mockedList = mock(List.class);
在幕後,Mockito mock
方法委託給另一個重載方法,該方法具有一組模擬默認設置:
public static <T> T mock(Class<T> classToMock) {
return mock(classToMock, withSettings());
}
讓我們看一下我們的默認設置:
public static MockSettings withSettings() {
return new MockSettingsImpl().defaultAnswer(RETURNS_DEFAULTS);
}
如我們所見,模擬對象的標准設置非常簡單。我們為模擬互動配置默認答案。通常,使用RETURNS_DEFAULTS
將返回一些空值。
擺脫這一點的重要一點是,如果需要,我們可以為模擬對象提供一組自定義設置。
在接下來的部分中,我們將看到一些方便使用的示例。
3.**提供不同的默認答案**
現在,我們對模擬設置有了更多的了解,讓我們看看如何更改模擬對象的默認返回值。
假設我們有一個非常簡單的模擬設置:
PizzaService service = mock(PizzaService.class);
Pizza pizza = service.orderHouseSpecial();
PizzaSize size = pizza.getSize();
當我們按預期運行此代碼時,將得到NullPointerException
因為未打樁的方法orderHouseSpecial
返回null
。
可以,但是有時在使用舊版代碼時,我們可能需要處理複雜的模擬對象層次結構,並且定位這些類型的異常發生的位置可能很耗時。
為了幫助我們解決這個問題,我們可以在模擬創建過程中通過模擬設置提供其他默認答案:
PizzaService pizzaService = mock(PizzaService.class, withSettings().defaultAnswer(RETURNS_SMART_NULLS));
通過使用RETURNS_SMART_NULLS
作為我們的默認答案,Mockito向我們提供了一條更有意義的錯誤消息,該錯誤消息向我們準確顯示了發生錯誤的存根的位置:
org.mockito.exceptions.verification.SmartNullPointerException:
You have a NullPointerException here:
-> at com.baeldung.mockito.mocksettings.MockSettingsUnitTest.whenServiceMockedWithSmartNulls_thenExceptionHasExtraInfo(MockSettingsUnitTest.java:45)
because this method call was *not* stubbed correctly:
-> at com.baeldung.mockito.mocksettings.MockSettingsUnitTest.whenServiceMockedWithSmartNulls_thenExceptionHasExtraInfo(MockSettingsUnitTest.java:44)
pizzaService.orderHouseSpecial();
確實可以在調試測試代碼時為我們節省一些時間。 Answers
枚舉還提供其他一些預配置的注意模擬答案:
-
RETURNS_DEEP_STUBS
–返回深層存根的答案–在使用Fluent API時可能很有用 -
RETURNS_MOCKS
–使用此答案將返回普通值,例如空集合或空字符串,然後,它嘗試返回模擬 -
CALLS_REAL_METHODS
–顧名思義,當我們使用此實現時,未打樁的方法將委託給實際的實現
4.命名模擬和詳細記錄
我們可以通過使用MockSettings
的name
方法為模擬對象命名。這對於調試特別有用,因為我們提供的名稱用於所有驗證錯誤:
PizzaService service = mock(PizzaService.class, withSettings()
.name("pizzaServiceMock")
.verboseLogging()
.defaultAnswer(RETURNS_SMART_NULLS));
在此示例中,我們通過使用verboseLogging()
方法將此命名功能與詳細日誌記錄結合在一起。
使用此方法可以實時記錄到標準輸出流中,以便對此模擬方法進行方法調用。同樣,可以在測試調試期間使用它來查找與模擬的錯誤交互。
運行測試時,我們將在控制台上看到一些輸出:
pizzaServiceMock.orderHouseSpecial();
invoked: -> at com.baeldung.mockito.mocksettings.MockSettingsUnitTest.whenServiceMockedWithNameAndVerboseLogging_thenLogsMethodInvocations(MockSettingsUnitTest.java:36)
has returned: "Mock for Pizza, hashCode: 366803687" (com.baeldung.mockito.fluentapi.Pizza$MockitoMock$168951489)
有趣的是,如果我們使用@Mock
註釋,我們的模擬將自動將字段名稱作為模擬名稱。
5.模擬額外的接口
有時,我們可能想指定模擬應實現的額外接口。同樣,當使用無法重構的舊代碼時,這可能很有用。
假設我們有一個特殊的接口:
public interface SpecialInterface {
// Public methods
}
和使用該接口的類:
public class SimpleService {
public SimpleService(SpecialInterface special) {
Runnable runnable = (Runnable) special;
runnable.run();
}
// More service methods
}
當然,這不是乾淨的代碼,但是如果我們被迫為此編寫單元測試,則很可能會遇到問題:
SpecialInterface specialMock = mock(SpecialInterface.class);
SimpleService service = new SimpleService(specialMock);
運行此代碼時,將得到ClassCastException
。為了解決這個問題,我們可以使用extraInterfaces
方法創建具有多種類型的模擬:
SpecialInterface specialMock = mock(SpecialInterface.class, withSettings()
.extraInterfaces(Runnable.class));
現在,我們的模擬創建代碼不會失敗,但是我們應該真正強調,強制轉換為未聲明的類型並不是很酷。
6.提供構造函數參數
在最後一個示例中,我們將看到如何使用MockSettings
調用帶有參數值的實際構造函數:
@Test
public void whenMockSetupWithConstructor_thenConstructorIsInvoked() {
AbstractCoffee coffeeSpy = mock(AbstractCoffee.class, withSettings()
.useConstructor("espresso")
.defaultAnswer(CALLS_REAL_METHODS));
assertEquals("Coffee name: ", "espresso", coffeeSpy.getName());
}
這次,Mockito在創建AbstractCoffee
模擬的實例時嘗試使用帶有String
值的構造函數。我們還配置了默認答案,以委託給實際的實現。
如果我們在構造函數中有一些邏輯要測試或觸發,以使我們的類處於某種特定狀態,這可能很有用。監視抽像類時,它也很有用。
7.結論
在本快速教程中,我們了解瞭如何使用其他模擬設置來創建模擬。
但是,我們應該重申,儘管這有時是有用的,並且可能是不可避免的,但在大多數情況下,我們應該努力使用簡單的模擬編寫簡單的測試。
與往常一樣,可以在GitHub上獲得本文的完整源代碼。