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.命名模擬和詳細記錄

    我們可以通過使用MockSettingsname方法為模擬對象命名。這對於調試特別有用,因為我們提供的名稱用於所有驗證錯誤:

    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上獲得本文的完整源代碼。