Mockito
是當前最流行的 單元測試 Mock
框架。採用 Mock
框架,咱們能夠 虛擬 出一個 外部依賴,下降測試 組件 之間的 耦合度,只注重代碼的 流程與結果,真正地實現測試目的。java
Mock
的中文譯爲仿製的,模擬的,虛假的。對於測試框架來講,即構造出一個模擬/虛假的對象,使咱們的測試能順利進行下去。android
Mock
測試就是在測試過程當中,對於某些 不容易構造(如 HttpServletRequest
必須在 Servlet
容器中才能構造出來)或者不容易獲取 比較複雜 的對象(如 JDBC
中的 ResultSet
對象),用一個 虛擬 的對象(Mock
對象)來建立,以便測試方法。編程
單元測試 是爲了驗證咱們的代碼運行正確性,咱們注重的是代碼的流程以及結果的正確與否。後端
對比真實運行代碼,可能其中有一些 外部依賴 的構建步驟相對麻煩,若是咱們仍是按照真實代碼的構建規則構造出外部依賴,會大大增長單元測試的工做,代碼也會參雜太多非測試部分的內容,測試用例顯得複雜難懂。緩存
採用 Mock
框架,咱們能夠 虛擬 出一個 外部依賴,只注重代碼的 流程與結果,真正地實現測試目的。安全
mock
對象的行爲;如圖所示,使用 Mockito
的大體流程以下:多線程
建立 外部依賴 的 Mock
對象, 而後將此 Mock
對象注入到 測試類 中;架構
執行 測試代碼;app
校驗 測試代碼 是否執行正確。框架
在 Module
的 build.gradle
中添加以下內容:
dependencies {
//Mockito for unit tests
testImplementation "org.mockito:mockito-core:2.+"
//Mockito for Android tests
androidTestImplementation 'org.mockito:mockito-android:2.+'
}
複製代碼
這裏稍微解釋下:
mockito-core
: 用於 本地單元測試,其測試代碼路徑位於 module-name/src/test/java/
mockito-android
: 用於 設備測試,即須要運行 android
設備進行測試,其測試代碼路徑位於 module-name/src/androidTest/java/
mockito-core最新版本能夠在 Maven 中查詢:mockito-core。 mockito-android最新版本能夠在 Maven 中查詢:mockito-android
普通單元測試使用 mockito(mockito-core)
,路徑:module-name/src/test/java/
這裏摘用官網的 Demo
:
import static org.mockito.Mockito.*;
// Mock creation
List mockedList = mock(List.class);
// Use mock object - it does not throw any "unexpected interaction" exception
mockedList.add("one"); //調用了add("one")行爲
mockedList.clear(); //調用了clear()行爲
// Selective, explicit, highly readable verification
verify(mockedList).add("one"); // 檢驗add("one")是否已被調用
verify(mockedList).clear(); // 檢驗clear()是否已被調用
複製代碼
這裏 mock
了一個 List
(這裏只是爲了用做 Demo
示例,一般對於 List
這種簡單的類對象建立而言,直接 new
一個真實的對象便可,無需進行 mock
),verify()
會檢驗對象是否在前面已經執行了相關行爲,這裏 mockedList
在 verify
以前已經執行了 add("one")
和 clear()
行爲,因此verify()
會經過。
// you can mock concrete classes, not only interfaces
LinkedList mockedList = mock(LinkedList.class);
// stubbing appears before the actual execution
when(mockedList.get(0)).thenReturn("first");
// the following prints "first"
System.out.println(mockedList.get(0));
// the following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));
複製代碼
這裏對幾個比較重要的點進行解析:
when(mockedList.get(0)).thenReturn("first")
這句話 Mockito
會解析爲:當對象 mockedList
調用 get()
方法,而且參數爲 0
時,返回結果爲"first"
,這至關於定製了咱們 mock
對象的行爲結果(mock LinkedList
對象爲 mockedList
,指定其行爲 get(0)
,則返回結果爲 "first"
)。
mockedList.get(999)
因爲 mockedList
沒有指定 get(999)
的行爲,因此其結果爲 null
。由於 Mockito
的底層原理是使用 cglib
動態生成一個 代理類對象,所以,mock
出來的對象其實質就是一個 代理,該代理在 沒有配置/指定行爲 的狀況下,默認返回 空值。
上面的 Demo
使用的是 靜態方法 mock()
模擬出一個實例,咱們還能夠經過註解 @Mock
也模擬出一個實例:
@Mock
private Intent mIntent;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Test
public void mockAndroid(){
Intent intent = mockIntent();
assertThat(intent.getAction()).isEqualTo("com.yn.test.mockito");
assertThat(intent.getStringExtra("Name")).isEqualTo("Whyn");
}
private Intent mockIntent(){
when(mIntent.getAction()).thenReturn("com.yn.test.mockito");
when(mIntent.getStringExtra("Name")).thenReturn("Whyn");
return mIntent;
}
複製代碼
對於標記有 @Mock
, @Spy
, @InjectMocks
等註解的成員變量的 初始化 到目前爲止有 2
種方法:
對 JUnit
測試類添加 @RunWith(MockitoJUnitRunner.class)
在標示有 @Before
方法內調用初始化方法:MockitoAnnotations.initMocks(Object)
上面的測試用例,對於 @Mock
等註解的成員變量的初始化又多了一種方式 MockitoRule
。規則 MockitoRule
會自動幫咱們調用 MockitoAnnotations.initMocks(this)
去 實例化 出 註解 的成員變量,咱們就無需手動進行初始化了。
// You can mock concrete classes, not just interfaces
LinkedList mockedList = mock(LinkedList.class);
// Stubbing
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());
// Following prints "first"
System.out.println(mockedList.get(0));
// Following throws runtime exception
System.out.println(mockedList.get(1));
// Following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));
// Although it is possible to verify a stubbed invocation, usually it's just redundant
// If your code cares what get(0) returns, then something else breaks (often even before verify() gets executed).
// If your code doesn't care what get(0) returns, then it should not be stubbed. Not convinced? See here.
verify(mockedList).get(0);
複製代碼
對於全部方法,mock
對象默認返回 null
,原始類型/原始類型包裝類 默認值,或者 空集合。好比對於 int/Integer
類型,則返回 0
,對於 boolean/Boolean
則返回 false
。
行爲配置(stub
)是能夠被複寫的:好比一般的對象行爲是具備必定的配置,可是測試方法能夠複寫這個行爲。請謹記行爲複寫可能代表潛在的行爲太多了。
一旦配置了行爲,方法老是會返回 配置值,不管該方法被調用了多少次。
最後一次行爲配置是更加劇要的,當你爲一個帶有相同參數的相同方法配置了不少次,最後一次起做用。
Mockito
經過參數對象的 equals()
方法來驗證參數是否一致,當須要更多的靈活性時,可使用參數匹配器:
// Stubbing using built-in anyInt() argument matcher
when(mockedList.get(anyInt())).thenReturn("element");
// Stubbing using custom matcher (let's say isValid() returns your own matcher implementation):
when(mockedList.contains(argThat(isValid()))).thenReturn("element");
// Following prints "element"
System.out.println(mockedList.get(999));
// You can also verify using an argument matcher
verify(mockedList).get(anyInt());
// Argument matchers can also be written as Java 8 Lambdas
verify(mockedList).add(argThat(someString -> someString.length() > 5));
複製代碼
參數匹配器 容許更加靈活的 驗證 和 行爲配置。更多 內置匹配器 和 自定義參數匹配器 例子請參考:ArgumentMatchers
,MockitoHamcrest
注意:若是使用了參數匹配器,那麼全部的參數都須要提供一個參數匹配器。
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
// Above is correct - eq() is also an argument matcher
verify(mock).someMethod(anyInt(), anyString(), "third argument");
// Above is incorrect - exception will be thrown because third argument is given without an argument matcher.
複製代碼
相似 anyObject()
,eq()
這類匹配器並不返回匹配數值。他們內部記錄一個 匹配器堆棧 並返回一個空值(一般爲 null
)。這個實現是爲了匹配 java
編譯器的 靜態類型安全,這樣作的後果就是你不能在 檢驗/配置方法 外使用 anyObject()
,eq()
等方法。
LinkedList mockedList = mock(LinkedList.class);
// Use mock
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice");
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");
// Follow two verifications work exactly the same - times(1) is used by default
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");
// Exact number of invocations verification
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");
// Verification using never(). never() is an alias to times(0)
verify(mockedList, never()).add("never happened");
// Verification using atLeast()/atMost()
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("three times");
verify(mockedList, atMost(5)).add("three times");
複製代碼
校驗次數方法經常使用的有以下幾個:
Method | Meaning |
---|---|
times(n) | 次數爲n,默認爲1(times(1)) |
never() | 次數爲0,至關於times(0) |
atLeast(n) | 最少n次 |
atLeastOnce() | 最少一次 |
atMost(n) | 最多n次 |
doThrow(new RuntimeException()).when(mockedList).clear();
// following throws RuntimeException
mockedList.clear();
複製代碼
有時對於一些行爲,有前後順序之分,因此,當咱們在校驗時,就須要考慮這個行爲的前後順序:
// A. Single mock whose methods must be invoked in a particular order
List singleMock = mock(List.class);
// Use a single mock
singleMock.add("was added first");
singleMock.add("was added second");
// Create an inOrder verifier for a single mock
InOrder inOrder = inOrder(singleMock);
// Following will make sure that add is first called with "was added first, then with "was added second"
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");
// B. Multiple mocks that must be used in a particular order
List firstMock = mock(List.class);
List secondMock = mock(List.class);
// Use mocks
firstMock.add("was called first");
secondMock.add("was called second");
// Create inOrder object passing any mocks that need to be verified in order
InOrder inOrder = inOrder(firstMock, secondMock);
// Following will make sure that firstMock was called before secondMock
inOrder.verify(firstMock).add("was called first");
inOrder.verify(secondMock).add("was called second");
複製代碼
對於同一個方法,若是咱們想讓其在 屢次調用 中分別 返回不一樣 的數值,那麼就可使用存根連續調用:
when(mock.someMethod("some arg"))
.thenThrow(new RuntimeException())
.thenReturn("foo");
// First call: throws runtime exception:
mock.someMethod("some arg");
// Second call: prints "foo"
System.out.println(mock.someMethod("some arg"));
// Any consecutive call: prints "foo" as well (last stubbing wins).
System.out.println(mock.someMethod("some arg"));
複製代碼
也可使用下面更簡潔的存根連續調用方法:
when(mock.someMethod("some arg")).thenReturn("one", "two", "three");
複製代碼
注意:存根連續調用要求必須使用鏈式調用,若是使用的是同個方法的多個存根配置,那麼只有最後一個起做用(覆蓋前面的存根配置)。
// All mock.someMethod("some arg") calls will return "two"
when(mock.someMethod("some arg").thenReturn("one")
when(mock.someMethod("some arg").thenReturn("two")
複製代碼
對於 返回類型 爲 void
的方法,存根要求使用另外一種形式的 when(Object)
函數,由於編譯器要求括號內不能存在 void
方法。
例如,存根一個返回類型爲 void
的方法,要求調用時拋出一個異常:
doThrow(new RuntimeException()).when(mockedList).clear();
// Following throws RuntimeException:
mockedList.clear();
複製代碼
前面使用的都是 mock
出來一個對象。這樣,當 沒有配置/存根 其具體行爲的話,結果就會返回 空類型。而若是使用 特務對象(spy
),那麼對於 沒有存根 的行爲,它會調用 原來對象 的方法。能夠把 spy
想象成局部 mock
。
List list = new LinkedList();
List spy = spy(list);
// Optionally, you can stub out some methods:
when(spy.size()).thenReturn(100);
// Use the spy calls *real* methods
spy.add("one");
spy.add("two");
// Prints "one" - the first element of a list
System.out.println(spy.get(0));
// Size() method was stubbed - 100 is printed
System.out.println(spy.size());
// Optionally, you can verify
verify(spy).add("one");
verify(spy).add("two");
複製代碼
注意:因爲 spy 是局部 mock,因此有時候使用 when(Object) 時,沒法作到存根做用。此時,就能夠考慮使用 doReturn() | Answer() | Throw() 這類方法進行存根:
List list = new LinkedList();
List spy = spy(list);
// Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
when(spy.get(0)).thenReturn("foo");
// You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);
複製代碼
spy
並非 真實對象 的 代理。相反的,它對傳遞過來的 真實對象 進行 克隆。因此,對 真實對象 的任何操做,spy
對象並不會感知到。同理,對 spy
對象的任何操做,也不會影響到 真實對象。
固然,若是使用 mock
進行對象的 局部 mock
,經過 doCallRealMethod() | thenCallRealMethod()
方法也是能夠的:
// You can enable partial mock capabilities selectively on mocks:
Foo mock = mock(Foo.class);
// Be sure the real implementation is 'safe'.
// If real implementation throws exceptions or depends on specific state of the object then you're in trouble.
when(mock.someMethod()).thenCallRealMethod();
複製代碼
以 行爲驅動開發 的格式使用 //given //when //then 註釋爲測試用法基石編寫測試用例,這正是 Mockito
官方編寫測試用例方法,強烈建議使用這種方式測試編寫。
import static org.mockito.BDDMockito.*;
Seller seller = mock(Seller.class);
Shop shop = new Shop(seller);
public void shouldBuyBread() throws Exception {
// Given
given(seller.askForBread()).willReturn(new Bread());
// When
Goods goods = shop.buyBread();
// Then
assertThat(goods, containBread());
}
複製代碼
// Will print a custom message on verification failure
verify(mock, description("This will print on failure")).someMethod();
// Will work with any verification mode
verify(mock, times(2).description("someMethod should be called twice")).someMethod();
複製代碼
構造器,方法,成員變量依賴注入 使用 @InjectMock
註解時,Mockito
會檢查 類構造器,方法 或 成員變量,依據它們的 類型 進行自動 mock
。
public class InjectMockTest {
@Mock
private User user;
@Mock
private ArticleDatabase database;
@InjectMocks
private ArticleManager manager;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Test
public void testInjectMock() {
// Calls addListener with an instance of ArticleListener
manager.initialize();
// Validate that addListener was called
verify(database).addListener(any(ArticleListener.class));
}
public static class ArticleManager {
private User user;
private ArticleDatabase database;
public ArticleManager(User user, ArticleDatabase database) {
super();
this.user = user;
this.database = database;
}
public void initialize() {
database.addListener(new ArticleListener());
}
}
public static class User {
}
public static class ArticleListener {
}
public static class ArticleDatabase {
public void addListener(ArticleListener listener) {
}
}
}
複製代碼
成員變量 manager
類型爲 ArticleManager
,它的上面標識別了 @InjectMocks
。這意味着要 mock
出 manager
,Mockito
須要先自動 mock
出 ArticleManager
所需的 構造參數(即:user
和 database
),最終 mock
獲得一個 ArticleManager
,賦值給 manager
。
ArgumentCaptor
容許在 verify
的時候獲取 方法參數內容,這使得咱們能在 測試過程 中能對 調用方法參數 進行 捕捉 並 測試。
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Captor
private ArgumentCaptor<List<String>> captor;
@Test
public void testArgumentCaptor(){
List<String> asList = Arrays.asList("someElement_test", "someElement");
final List<String> mockedList = mock(List.class);
mockedList.addAll(asList);
verify(mockedList).addAll(captor.capture()); // When verify,you can capture the arguments of the calling method
final List<String> capturedArgument = captor.getValue();
assertThat(capturedArgument, hasItem("someElement"));
}
複製代碼
mock
靜態方法;mock
構造器;mock
equals()
和 hashCode()
方法。歡迎關注技術公衆號:零壹技術棧
本賬號將持續分享後端技術乾貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分佈式和微服務,架構學習和進階等學習資料和文章。