Mockito 是一個強大的用於 Java 開發的模擬測試框架, 經過 Mockito 咱們能夠建立和配置 Mock 對象, 進而簡化有外部依賴的類的測試.
使用 Mockito 的大體流程以下:segmentfault
建立外部依賴的 Mock 對象, 而後將此 Mock 對象注入到測試類中.ruby
執行測試代碼.框架
校驗測試代碼是否執行正確.less
咱們已經知道了 Mockito 主要的功能就是建立 Mock 對象, 那麼什麼是 Mock 對象呢? 對 Mock 對象不是很瞭解的朋友, 能夠參考這篇文章.
如今咱們對 Mock 對象有了必定的瞭解了, 那麼天然就會有人問了, 爲何要使用 Mock 對象? 使用它有什麼好處呢?
下面咱們以一個簡單的例子來展現一下 Mock 對象到底有什麼用.
假設咱們正在編寫一個銀行的服務 BankService, 這個服務的依賴關係以下:單元測試
當咱們須要測試 BankService 服務時, 該真麼辦呢?
一種方法是構建真實的 BankDao, DB, AccountService 和 AuthService 實例, 而後注入到 BankService 中.
不用我說, 讀者們也確定明白, 這是一種既笨重又繁瑣的方法, 徹底不符合單元測試的精神. 那麼還有一種更加優雅的方法嗎? 天然是有的, 那就是咱們今天的主角 Mock Object. 下面來看一下使用 Mock 對象後的框架圖:測試
咱們看到, BankDao, AccountService 和 AuthService 都被咱們使用了虛擬的對象(Mock 對象) 來替換了, 所以咱們就能夠對 BankService 進行測試, 而不須要關注它的複雜的依賴了.gradle
爲了簡潔期間, 下面的代碼都省略了靜態導入 import static org.mockito.Mockito.*;ui
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>2.0.111-beta</version> </dependency>
@Test public void createMockObject() { // 使用 mock 靜態方法建立 Mock 對象. List mockedList = mock(List.class); Assert.assertTrue(mockedList instanceof List); // mock 方法不只能夠 Mock 接口類, 還能夠 Mock 具體的類型. ArrayList mockedArrayList = mock(ArrayList.class); Assert.assertTrue(mockedArrayList instanceof List); Assert.assertTrue(mockedArrayList instanceof ArrayList); }
如上代碼所示, 咱們調用了 mock 靜態方法來建立一個 Mock 對象. mock 方法接收一個 class 類型, 即咱們須要 mock 的類型.spa
當咱們有了一個 Mock 對象後, 咱們能夠定製它的具體的行爲. 例如:code
@Test
public void configMockObject() {
List mockedList = mock(List.class); // 咱們定製了當調用 mockedList.add("one") 時, 返回 true when(mockedList.add("one")).thenReturn(true); // 當調用 mockedList.size() 時, 返回 1 when(mockedList.size()).thenReturn(1); Assert.assertTrue(mockedList.add("one")); // 由於咱們沒有定製 add("two"), 所以返回默認值, 即 false. Assert.assertFalse(mockedList.add("two")); Assert.assertEquals(mockedList.size(), 1); Iterator i = mock(Iterator.class); when(i.next()).thenReturn("Hello,").thenReturn("Mockito!"); String result = i.next() + " " + i.next(); //assert Assert.assertEquals("Hello, Mockito!", result); }
咱們使用 when(...).thenReturn(...) 方法鏈來定義一個行爲, 例如 "when(mockedList.add("one")).thenReturn(true)" 表示: 當調用了mockedList.add("one"), 那麼返回 true.. 而且要注意的是, when(...).thenReturn(...) 方法鏈不單單要匹配方法的調用, 並且要方法的參數同樣才行.
並且有趣的是, when(...).thenReturn(...) 方法鏈能夠指定多個返回值, 當這樣作後, 若是屢次調用指定的方法, 那麼這個方法會依次返回這些值. 例如 "when(i.next()).thenReturn("Hello,").thenReturn("Mockito!");", 這句代碼表示: 第一次調用 i.next() 時返回 "Hello,", 第二次調用 i.next() 時返回 "Mockito!".
上面的例子咱們展現了方法調用返回值的定製, 那麼咱們能夠指定一個拋出異常嗎? 固然能夠的, 例如:
@Test(expected = NoSuchElementException.class) public void testForIOException() throws Exception { Iterator i = mock(Iterator.class); when(i.next()).thenReturn("Hello,").thenReturn("Mockito!"); // 1 String result = i.next() + " " + i.next(); // 2 Assert.assertEquals("Hello, Mockito!", result); doThrow(new NoSuchElementException()).when(i).next(); // 3 i.next(); // 4 }
上面代碼的第一第二步咱們已經很熟悉了, 接下來第三部咱們使用了一個新語法: doThrow(ExceptionX).when(x).methodCall
, 它的含義是: 當調用了 x.methodCall 方法後, 拋出異常 ExceptionX.
所以 doThrow(new NoSuchElementException()).when(i).next() 的含義就是: 當第三次調用 i.next() 後, 拋出異常 NoSuchElementException.(由於 i 這個迭代器只有兩個元素)
Mockito 會追蹤 Mock 對象的所用方法調用和調用方法時所傳遞的參數. 咱們能夠經過 verify() 靜態方法來來校驗指定的方法調用是否知足斷言. 語言描述有一點抽象, 下面咱們仍然以代碼來講明一下.
@Test
public void testVerify() { List mockedList = mock(List.class); mockedList.add("one"); mockedList.add("two"); mockedList.add("three times"); mockedList.add("three times"); mockedList.add("three times"); when(mockedList.size()).thenReturn(5); Assert.assertEquals(mockedList.size(), 5); verify(mockedList, atLeastOnce()).add("one"); verify(mockedList, times(1)).add("two"); verify(mockedList, times(3)).add("three times"); verify(mockedList, never()).isEmpty(); }
上面的例子前半部份沒有什麼特別的, 咱們關注後面的:
verify(mockedList, atLeastOnce()).add("one"); verify(mockedList, times(1)).add("two"); verify(mockedList, times(3)).add("three times"); verify(mockedList, never()).isEmpty();
讀者根據代碼也應該能夠猜想出它的含義了, 很簡單:
第一句校驗 mockedList.add("one") 至少被調用了 1 次(atLeastOnce)
第二句校驗 mockedList.add("two") 被調用了 1 次(times(1))
第三句校驗 mockedList.add("three times") 被調用了 3 次(times(3))
第四句校驗 mockedList.isEmpty() 從未被調用(never)
Mockito 提供的 spy 方法能夠包裝一個真實的 Java 對象, 並返回一個包裝後的新對象. 若沒有特別配置的話, 對這個新對象的全部方法調用, 都會委派給實際的 Java 對象. 例如:
@Test public void testSpy() { List list = new LinkedList(); List spy = spy(list); // 對 spy.size() 進行定製. when(spy.size()).thenReturn(100); spy.add("one"); spy.add("two"); // 由於咱們沒有對 get(0), get(1) 方法進行定製, // 所以這些調用實際上是調用的真實對象的方法. Assert.assertEquals(spy.get(0), "one"); Assert.assertEquals(spy.get(1), "two"); Assert.assertEquals(spy.size(), 100); }
這個例子中咱們實例化了一個 LinkedList 對象, 而後使用 spy() 方法對 list 對象進行部分模擬. 接着咱們使用 when(...).thenReturn(...) 方法鏈來規定 spy.size() 方法返回值是 100. 隨後咱們給 spy 添加了兩個元素, 而後再 調用 spy.get(0) 獲取第一個元素.
這裏有意思的地方是: 由於咱們沒有定製 add("one"), add("two"), get(0), get(1), 所以經過 spy 調用這些方法時, 其實是委派給 list 對象來調用的.
然而咱們 定義了 spy.size() 的返回值, 所以當調用 spy.size() 時, 返回 100.
Mockito 允准咱們捕獲一個 Mock 對象的方法調用所傳遞的參數, 例如:
@Test public void testCaptureArgument() { List<String> list = Arrays.asList("1", "2"); List mockedList = mock(List.class); ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class); mockedList.addAll(list); verify(mockedList).addAll(argument.capture()); Assert.assertEquals(2, argument.getValue().size()); Assert.assertEquals(list, argument.getValue()); }
咱們經過 verify(mockedList).addAll(argument.capture()) 語句來獲取 mockedList.addAll 方法所傳遞的實參 list.