遷移到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 中文文檔