Mockito

遷移到Mockito 2

爲了繼續改進Mockito並進一步提高單元測試體驗,我們希望您升級到2.1.0!Mockito遵循語義版本控制,僅在主版本升級時才包含突破性更改。
在一個庫的生命週期中,爲了推出一系列改變現有行爲甚至改變API的全新功能,突破性變化是必要的。有關新版本的全面指南,包括不兼容的更改,
請參閱“Mockito 2的新功能”Wiki頁面。我們希望你喜歡Mockito 2!

Mockito Android支持

使用Mockito版本2.6.1,我們提供“native”Android支持。要啓用Android支持,請將“mockito-android”庫作爲依賴項添加到您的項目中。
此工件發佈到同一個Mockito組織,可以按照以下方式導入到Android:

repositories {
   jcenter()
}
dependencies {
   testCompile "org.mockito:mockito-core:+"
   androidTestCompile "org.mockito:mockito-android:+"
}

您可以使用上述“testCompile”範圍中的“mockito-core”工件繼續在常規虛擬機上運行相同的單元測試。
請注意,由於Android VM的限制,您無法使用Android上的內嵌模擬器
如果您在Android上遇到mocking問題,請在官方問題跟蹤棧上提交問題。提供您正在處理的Android版本和項目的依賴關係。

無配置的內聯模擬製作

從版本2.7.6開始,我們提供“mockito-inline”工件,可以在不配置MockMaker擴展文件的情況下啓用內聯模擬
要使用它,添加mockito-inline而不是mockito-core工件,如下所示:

 repositories {
   jcenter()
 }
 dependencies {
   testCompile "org.mockito:mockito-inline:+"
 }

請注意,當將內聯模擬功能集成到默認模擬器中時,該工件可能會被廢除。

有關內聯模擬製作的更多信息,請參見第39節

我們來驗證一些行爲!

以下示例mock List,因爲大多數人熟悉它的接口(如add(),get(),clear()方法)。

實際上,請不要mock List類。請改用真實的實例代替。

 // 靜態導入會使代碼更簡潔
 import static org.mockito.Mockito.*;

 // 創建mock對象
 List mockedList = mock(List.class);

 // 使用mock對象
 mockedList.add("one");
 mockedList.clear();

 // 驗證
 verify(mockedList).add("one"); 
 verify(mockedList).clear(); // 驗證clear方法執行過一次,等同於: verify(mockedList, times(1)).clear();

一旦創建,模擬器將記住所有的交互。然後,您可以選擇性地驗證您感興趣的任何交互。

如何做一些測試樁?


 // 你可以模擬具體的類,而不僅僅是接口
 LinkedList mockedList = mock(LinkedList.class);

 // 測試樁,存根行爲
 when(mockedList.get(0)).thenReturn("first");
 when(mockedList.get(1)).thenThrow(new RuntimeException());

 // 以下輸出“first”
 System.out.println(mockedList.get(0));

 // 以下拋出RuntimeException
 System.out.println(mockedList.get(1));

 // 因爲get(999)沒有存根,因此以下輸出null
 System.out.println(mockedList.get(999));

 // 雖然可以驗證一個存根調用,但通常它是多餘的
 // 如果你的代碼關心get(0)返回,那麼其他的東西就會中斷(通常甚至在執行verify()之前)。
 // 如果你的代碼不在乎什麼get(0)返回,那麼它不應該被存根。不相信看這裏。
 verify(mockedList).get(0);
  • 默認情況下,所有的函數都有返回值,mock函數將根據需要返回null,原始/原始包裝值或空集合。例如對於int/Integer爲0,對於boolean/Boolean爲false;
  • 測試樁函數可以被覆蓋:例如,常見的測試樁函數可以用於初始化夾具,但測試方法可以覆蓋它。請注意,覆寫測試樁函數是一種可能存在潛在問題的做法;
  • 一旦測試樁函數被調用,該函數將會一致返回固定的值;
  • 最後一次調用測試樁函數有時候極爲重要 - 當你用同樣的參數多次調用相同的方法,最後一次調用可能是你所感興趣的。也就是說:樁的順序是重要的,但它很少有意義,例如當使用完全相同的方法調用或有時候使用參數匹配器等時。

參數匹配器

Mockito以自然的Java風格來驗證參數值: 使用equals()函數。有時,當需要額外的靈活性時你可能需要使用參數匹配器。

 // 使用內置的anyInt()參數匹配器
 when(mockedList.get(anyInt())).thenReturn("element");

 // 使用自定義的參數匹配器( 在isValid()函數中返回你自己的匹配器實現):
 when(mockedList.contains(argThat(isValid()))).thenReturn("element");

 // 以下輸出element
 System.out.println(mockedList.get(999));

 // 您還可以使用參數匹配器驗證
 verify(mockedList).get(anyInt());

 // 參數匹配器也可以寫成Java 8 Lambdas
 verify(mockedList).add(argThat(someString -> someString.length() > 5));

參數匹配器使驗證和測試樁變得更靈活。點擊這裏這裏
查看更多內置的匹配器以及自定義參數匹配器或者hamcrest 匹配器的示例。

要獲取有關自定義參數匹配器的信息,請查看javadoc for ArgumentMatcher類。

使用複雜的參數匹配是合理的。使用equals()anyX()的匹配器會使得測試代碼更簡潔、簡單。有時,會迫使你重構代碼以使用equals()匹配或者實現equals()函數來幫助你進行測試。

另外,請參閱ArgumentCaptor類的第15節或javadoc。ArgumentCaptor是一個參數匹配器的特殊實現,它捕獲參數值以進一步的斷言。

參數匹配器的注意點:

如果使用參數匹配器,所有參數都必須由匹配器提供。

以下示例展示瞭如何多次應用於測試樁函數的驗證:

verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
// 上述代碼是正確的,因爲eq()也是一個參數匹配器

verify(mock).someMethod(anyInt(), anyString(), "third argument");
// 上述代碼是錯誤的,因爲所有參數必須由匹配器提供,而參數"third argument"並非由參數匹配器提供,因此會拋出異常

anyObject(), eq()這樣的匹配器函數不會返回匹配器。在內部,它們在堆棧上記錄一個匹配器,並返回一個虛擬值(通常爲null)。這種實現是由於java編譯器強加的靜態類型的安全性。
結果是你不能在驗證或者測試樁函數方法之外使用anyObject(), eq()方法。

驗證確切的調用次數/至少x/從未

 //using mock
 mockedList.add("once");

 mockedList.add("twice");
 mockedList.add("twice");

 mockedList.add("three times");
 mockedList.add("three times");
 mockedList.add("three times");

 // 下面的兩個驗證函數效果一樣,因爲verify默認驗證的就是times(1)
 verify(mockedList).add("once");
 verify(mockedList, times(1)).add("once");

 // 驗證函數具體的執行次數
 verify(mockedList, times(2)).add("twice");
 verify(mockedList, times(3)).add("three times");

 // 使用never()進行驗證,never相當於times(0)
 verify(mockedList, never()).add("never happened");

 // 使用atLeast()/atMost()進行驗證
 verify(mockedList, atLeastOnce()).add("three times");
 verify(mockedList, atLeast(2)).add("three times");
 verify(mockedList, atMost(5)).add("three times");

verify函數默認驗證的是執行了times(1),也就是某個測試函數是否執行了1次.因此,times(1)通常可以省略。

爲返回值爲void的函數拋出異常


doThrow(new RuntimeException()).when(mockedList).clear();

// 以下拋出RuntimeException:
mockedList.clear(); 

關於doThrow()|doAnswer()等函數族的信息請閱讀第12章節

驗證順序

 // A. 必須以特定順序調用其方法的單一mock
 List singleMock = mock(List.class);

 // 使用單一的mock對象
 singleMock.add("was added first");
 singleMock.add("was added second");

 // 爲單個模擬創建一個inOrder驗證器
 InOrder inOrder = inOrder(singleMock);

 // 確保add函數首先執行的是add("was added first"),然後纔是add("was added second")
 inOrder.verify(singleMock).add("was added first");
 inOrder.verify(singleMock).add("was added second");

 // B. 必須以特定順序使用多個mock
 List firstMock = mock(List.class);
 List secondMock = mock(List.class);

 // 使用多個mock對象
 firstMock.add("was called first");
 secondMock.add("was called second");

 // 爲這兩個Mock對象創建inOrder驗證器
 InOrder inOrder = inOrder(firstMock, secondMock);

 // 以下將確保在secondMock之前調用firstMock
 inOrder.verify(firstMock).add("was called first");
 inOrder.verify(secondMock).add("was called second");

 // 哦,A B可以隨意混合在一起
 ```

驗證執行順序是非常靈活的-**你不需要一個一個的驗證所有交互**,只需要驗證你感興趣的對象即可。 
另外,你可以僅通過那些需要驗證順序的mock對象來創建InOrder對象。

### 確保交互操作從未發生在mock對象上

```java
 // 使用Mock對象 - 只有 mockOne 被交互
 mockOne.add("one");

 // 普通驗證
 verify(mockOne).add("one");

 // 驗證該方法從未在mock對象中被調用
 verify(mockOne, never()).add("two");

 // 驗證其他mock對象沒有互動
 verifyZeroInteractions(mockTwo, mockThree);





<div class="se-preview-section-delimiter"></div>

查找冗餘的調用


 //using mocks
 mockedList.add("one");
 mockedList.add("two");

 verify(mockedList).add("one");

 // 下面的驗證將會失敗
 verifyNoMoreInteractions(mockedList);




<div class="se-preview-section-delimiter"></div>

一些用戶可能會在頻繁地使用verifyNoMoreInteractions(),甚至在每個測試函數中都用。但是verifyNoMoreInteractions()並不建議在每個測試函數中都使用。
verifyNoMoreInteractions()在交互測試套件中只是一個便利的驗證,它的作用是當你需要驗證是否存在冗餘調用時。濫用它將導致測試代碼的可維護性降低
你可以閱讀這篇文檔來了解更多相關信息。

另請參見never() - 它更加明確,並且很好地傳達意圖。

簡化mock對象的創建-@Mock註解

  • 最小化重複的創建代碼
  • 使測試類的代碼可讀性更高
  • 使驗證錯誤更容易閱讀,因爲字段名稱用於標識mock對象
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock private ArticleDatabase database;
    @Mock private UserProvider userProvider;

    private ArticleManager manager;




<div class="se-preview-section-delimiter"></div>

注意: 下面這句代碼需要在運行測試函數之前被調用,一般放到測試類的基類或者test runner中:

 MockitoAnnotations.initMocks(testClass);

你可以使用內置的runner: MockitoJUnitRunner 或者一個rule : MockitoRule
關於mock註解的更多信息可以閱讀MockitoAnnotations文檔。

參考鏈接: Mockito & Mockito 中文文檔

發佈了103 篇原創文章 · 獲贊 19 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章