Mockito 簡明教程

原文同步至 http://waylau.com/mockito-quick-start/html

Mock 測試是單元測試的重要方法之一。本文介紹了基於 Java 語言的 Mock 測試框架 -- Mockito 的使用。java

什麼是 Mock 測試

Mock 測試就是在測試過程當中,對於某些不容易構造(如 HttpServletRequest 必須在Servlet 容器中才能構造出來)或者不容易獲取比較複雜的對象(如 JDBC 中的ResultSet 對象),用一個虛擬的對象(Mock 對象)來建立以便測試的測試方法。git

Mock 最大的功能是幫你把單元測試的耦合分解開,若是你的代碼對另外一個類或者接口有依賴,它可以幫你模擬這些依賴,並幫你驗證所調用的依賴的行爲。github

好比一段代碼有這樣的依賴:app

當咱們須要測試A類的時候,若是沒有 Mock,則咱們須要把整個依賴樹都構建出來,而使用 Mock 的話就能夠將結構分解開,像下面這樣:框架

Mock 對象使用範疇

真實對象具備不可肯定的行爲,產生不可預測的效果,(如:股票行情,天氣預報) 真實對象很難被建立的 真實對象的某些行爲很難被觸發 真實對象實際上還不存在的(和其餘開發小組或者和新的硬件打交道)等等maven

使用 Mock 對象測試的關鍵步驟

使用一個接口來描述這個對象 在產品代碼中實現這個接口 在測試代碼中實現這個接口 在被測試代碼中只是經過接口來引用對象,因此它不知道這個引用的對象是真實對象,仍是 Mock 對象。ide

Java Mock 測試

目前,在 Java 陣營中主要的 Mock 測試工具備 MockitoJMockEasyMock 等。svn

關於這些框架的比較,不是本文的重點。本文着重介紹 Mockito 的使用。工具

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 具體類而不單止是接口
  • 一點註解語法糖 - @Mock
  • 乾淨的驗證錯誤是 - 點擊堆棧跟蹤,看看在測試中的失敗驗證;點擊異常的緣由來導航到代碼中的實際互動。堆棧跟蹤老是乾乾淨淨。
  • 容許靈活有序的驗證(例如:你任意有序 verify,而不是每個單獨的交互)
  • 支持「詳細的用戶號碼的時間」以及「至少一​​次」驗證
  • 靈活的驗證或使用參數匹配器的 stub (anyObject()anyString()refEq() 用於基於反射的相等匹配)
  • 容許建立自定義的參數匹配器或者使用現有的 hamcrest 匹配器

Mockito 入門

聲明 mockito 依賴

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

示例

1.驗證行爲

//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 將會記得全部的交互。你能夠選擇驗證你感興趣的任何交互

2.stubbing

//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。
  • Stubbing 能夠被覆蓋。
  • 一旦 stub,該方法將始終返回一個 stub 的值,不管它有多少次被調用。
  • 最後的 stubbing 是很重要的 - 當你使用相同的參數 stub 屢次一樣的方法。換句話說:stubbing 的順序是重要的,但它惟一有意義的卻不多,例如當 stubbing 徹底相同的方法調用,或者有時當參數匹配器的使用,等等。

3.參數匹配器

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.

4.調用額外的調用數字/at least x / never

//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) 能夠顯示的省略。

5.Stubbing void 方法處理異常

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

//following throws RuntimeException:
mockedList.clear();

6.有序的驗證

// 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 。

7. 確保 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);

8.尋找多餘的調用

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

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

//following verification will fail
verifyNoMoreInteractions(mockedList);

注意:不建議 verifyNoMoreInteractions() 在每一個測試方法中使用。 verifyNoMoreInteractions() 是從交互測試工具包一個方便的斷言。只有與它的相關時才使用它。濫用它致使難以維護。

9. 標準建立 mock 方式 - 使用 @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

10. Stubbing 連續調用(迭代器式的 stubbing)

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");

11. 回調 Stubbing

容許使用泛型 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"));

12. doReturn()|doThrow()| doAnswer()|doNothing()|doCallRealMethod() 家族方法

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(),當:

  • stub void 方法
  • stub 方法在 spy 對象(見下面)
  • 能夠不止一次的 stub 相同的方法,在測試的中期來改變 mock 的行爲

但你更加傾向於使用這些方法來代替 when(),在全部的 stubbing 調用。能夠閱讀更多關於這些方法的描述:

doReturn(Object)

doThrow(Throwable...)

doThrow(Class)

doAnswer(Answer)

doNothing()

doCallRealMethod()

參考

相關文章
相關標籤/搜索