原文同步至 http://waylau.com/mockito-quick-start/html
Mock 測試是單元測試的重要方法之一。本文介紹了基於 Java 語言的 Mock 測試框架 -- Mockito 的使用。java
Mock 測試就是在測試過程當中,對於某些不容易構造(如 HttpServletRequest 必須在Servlet 容器中才能構造出來)或者不容易獲取比較複雜的對象(如 JDBC 中的ResultSet 對象),用一個虛擬的對象(Mock 對象)來建立以便測試的測試方法。git
Mock 最大的功能是幫你把單元測試的耦合分解開,若是你的代碼對另外一個類或者接口有依賴,它可以幫你模擬這些依賴,並幫你驗證所調用的依賴的行爲。github
好比一段代碼有這樣的依賴:app
當咱們須要測試A類的時候,若是沒有 Mock,則咱們須要把整個依賴樹都構建出來,而使用 Mock 的話就能夠將結構分解開,像下面這樣:框架
真實對象具備不可肯定的行爲,產生不可預測的效果,(如:股票行情,天氣預報) 真實對象很難被建立的 真實對象的某些行爲很難被觸發 真實對象實際上還不存在的(和其餘開發小組或者和新的硬件打交道)等等maven
使用一個接口來描述這個對象 在產品代碼中實現這個接口 在測試代碼中實現這個接口 在被測試代碼中只是經過接口來引用對象,因此它不知道這個引用的對象是真實對象,仍是 Mock 對象。ide
目前,在 Java 陣營中主要的 Mock 測試工具備 Mockito,JMock,EasyMock 等。svn
關於這些框架的比較,不是本文的重點。本文着重介紹 Mockito 的使用。工具
Mockito 是美味的 Java 單元測試 Mock 框架,開源。
大多 Java Mock 庫如 EasyMock 或 JMock 都是 expect-run-verify (指望-運行-驗證)方式,而 Mockito 則使用更簡單,更直觀的方法:在執行後的互動中提問。使用 Mockito,你能夠驗證任何你想要的。而那些使用 expect-run-verify 方式的庫,你經常被迫查看無關的交互。
非 expect-run-verify 方式 也意味着,Mockito 無需準備昂貴的前期啓動。他們的目標是透明的,讓開發人員專一於測試選定的行爲。
Mockito 擁有的很是少的 API,全部開始使用 Mockito,幾乎沒有時間成本。由於只有一種創造 mock 的方式。只要記住,在執行前 stub,然後在交互中驗證。你很快就會發現這樣 TDD java 代碼是多麼天然。
相似 EasyMock 的語法來的,因此你能夠放心地重構。Mockito 並不須要「expectation(指望)」的概念。只有 stub 和驗證。
Mockito 實現了 Gerard Meszaros 所謂的 Test Spy.
@Mock
verify
,而不是每個單獨的交互)anyObject()
,anyString()
或 refEq()
用於基於反射的相等匹配)Gradle 用戶可使用:
repositories { jcenter() } dependencies { testCompile "org.mockito:mockito-core:1.+" }
Maven 用戶可使用:http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.mockito%22%2C%20a%3A%22mockito-core%22
Mockito 自動發佈到 http://jcenter.bintray.com/org/mockito/mockito-core/ 並同步到 Maven Central Repository
//Let's import Mockito statically so that the code looks clearer import static org.mockito.Mockito.*; //mock creation List mockedList = mock(List.class); //using mock object mockedList.add("one"); mockedList.clear(); //verification verify(mockedList).add("one"); verify(mockedList).clear();
一旦建立 mock 將會記得全部的交互。你能夠選擇驗證你感興趣的任何交互
//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);
Mockito 驗證參數值使用 Java 方式:經過使用 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());
參數匹配器容許靈活的驗證或 stubbing。點擊這裏查看更多內置的匹配器和自定義的參數匹配器/ hamcrest匹配器的例子。
自定義參數的匹配信息,請查看 Javadoc 中 ArgumentMatcher 類。
若是你正在使用參數的匹配,全部的參數都由匹配器來提供。
下面的示例演示驗證,但一樣適用於 stubbing:
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.
//using mock mockedList.add("once"); mockedList.add("twice"); mockedList.add("twice"); mockedList.add("three times"); mockedList.add("three times"); mockedList.add("three times"); //following 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("five times"); verify(mockedList, atMost(5)).add("three times");
times(1) 是默認的,所以,使用的 times(1) 能夠顯示的省略。
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); //using 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); //using 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"); // Oh, and A + B can be mixed together at will
有序驗證是爲了靈活 - 你沒必要一個接一個驗證全部的交互。
此外,您還能夠經過建立 InOrder 對象傳遞只與有序驗證相關的 mock 。
//using mocks - only mockOne is interacted mockOne.add("one"); //ordinary verification verify(mockOne).add("one"); //verify that method was never called on a mock verify(mockOne, never()).add("two"); //verify that other mocks were not interacted verifyZeroInteractions(mockTwo, mockThree);
//using mocks mockedList.add("one"); mockedList.add("two"); verify(mockedList).add("one"); //following verification will fail verifyNoMoreInteractions(mockedList);
注意:不建議 verifyNoMoreInteractions() 在每一個測試方法中使用。 verifyNoMoreInteractions() 是從交互測試工具包一個方便的斷言。只有與它的相關時才使用它。濫用它致使難以維護。
@Mock
註解最小化可重用 mock 建立代碼
使測試類更加可讀性
使驗證錯誤更加易讀,由於字段名稱用於惟一識別 mock
public class ArticleManagerTest {
@Mock private ArticleCalculator calculator; @Mock private ArticleDatabase database; @Mock private UserProvider userProvider; private ArticleManager manager;
在基礎類或者測試 runner 裏面,使用以下:
MockitoAnnotations.initMocks(testClass);
可使用內建 runner: MockitoJUnitRunner 或者 rule: MockitoRule
更多詳見 MockitoAnnotations
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");
容許使用泛型 Answer 接口。
然而,這是不包括在最初的 Mockito 另外一個有爭議的功能。咱們建議您只需用thenReturn() 或 thenThrow() 來 stubbing ,這在測試/測試驅動中應用簡潔與簡單的代碼足夠了。可是,若是你有一個須要 stub 到泛型 Answer 接口,這裏是一個例子:
when(mock.someMethod(anyString())).thenAnswer(new Answer() { Object answer(InvocationOnMock invocation) { Object[] args = invocation.getArguments(); Object mock = invocation.getMock(); return "called with arguments: " + args; } }); //the following prints "called with arguments: foo" System.out.println(mock.someMethod("foo"));
Stubbing void 方法,須要不一樣的 when(Object) ,由於編譯器不喜歡括號內無效的方法...
在 用於 Stubbing void 方法中,doThrow(Throwable...) 取代 stubVoid(Object)。主要緣由是提升可讀性和與 doAnswer() 保持一致性。
當你想用 stub void 方法 使用 doThrow():
doThrow(new RuntimeException()).when(mockedList).clear(); //following throws RuntimeException: mockedList.clear();
在調用 when() 的相應地方可使用 oThrow(), doAnswer(), doNothing(), doReturn() 和 doCallRealMethod(),當:
但你更加傾向於使用這些方法來代替 when(),在全部的 stubbing 調用。能夠閱讀更多關於這些方法的描述: