手把手教你 Mockito 的使用

什麼是 Mockito

Mockito 是一個強大的用於 Java 開發的模擬測試框架, 經過 Mockito 咱們能夠建立和配置 Mock 對象, 進而簡化有外部依賴的類的測試.
使用 Mockito 的大體流程以下:segmentfault

  • 建立外部依賴的 Mock 對象, 而後將此 Mock 對象注入到測試類中.框架

  • 執行測試代碼.單元測試

  • 校驗測試代碼是否執行正確.測試

爲何使用 Mockito

咱們已經知道了 Mockito 主要的功能就是建立 Mock 對象, 那麼什麼是 Mock 對象呢? 對 Mock 對象不是很瞭解的朋友, 能夠參考這篇文章.
如今咱們對 Mock 對象有了必定的瞭解了, 那麼天然就會有人問了, 爲何要使用 Mock 對象? 使用它有什麼好處呢?
下面咱們以一個簡單的例子來展現一下 Mock 對象到底有什麼用.
假設咱們正在編寫一個銀行的服務 BankService, 這個服務的依賴關係以下:spa

clipboard.png

當咱們須要測試 BankService 服務時, 該真麼辦呢?
一種方法是構建真實的 BankDao, DB, AccountService 和 AuthService 實例, 而後注入到 BankService 中.
不用我說, 讀者們也確定明白, 這是一種既笨重又繁瑣的方法, 徹底不符合單元測試的精神. 那麼還有一種更加優雅的方法嗎? 天然是有的, 那就是咱們今天的主角 Mock Object. 下面來看一下使用 Mock 對象後的框架圖:code

clipboard.png

咱們看到, BankDao, AccountService 和 AuthService 都被咱們使用了虛擬的對象(Mock 對象) 來替換了, 所以咱們就能夠對 BankService 進行測試, 而不須要關注它的複雜的依賴了.對象

Mockito 基本使用

爲了簡潔期間, 下面的代碼都省略了靜態導入 import static org.mockito.Mockito.*;blog

Maven 依賴

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.0.111-beta</version>
</dependency>

建立 Mock 對象

@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 的類型.接口

配置 Mock 對象

當咱們有了一個 Mock 對象後, 咱們能夠定製它的具體的行爲. 例如:three

@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 這個迭代器只有兩個元素)

校驗 Mock 對象的方法調用

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)

使用 spy() 部分模擬對象

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.

本文由 yongshun 發表於我的博客, 採用署名-非商業性使用-相同方式共享 3.0 中國大陸許可協議.
非商業轉載請註明做者及出處. 商業轉載請聯繫做者本人
Email: yongshun1228@gmail.com
本文標題爲: 手把手教你 Mockito 的使用
本文連接爲: https://segmentfault.com/a/11...

相關文章
相關標籤/搜索