手把手教你 Mockito 的使用
什么是 Mockito
Mockito 是一個強大的用于 Java 開發的模擬測試框架, 通過 Mockito 我們可以創建和配置 Mock 對象, 進而簡化有外部依賴的類的測試.
使用 Mockito 的大致流程如下:
創建外部依賴的 Mock 對象, 然后將此 Mock 對象注入到測試類中.
執行測試代碼.
校驗測試代碼是否執行正確.
為什么使用 Mockito
我們已經知道了 Mockito 主要的功能就是創建 Mock 對象, 那么什么是 Mock 對象呢? 對 Mock 對象不是很了解的朋友, 可以參考這篇文章.
現在我們對 Mock 對象有了一定的了解了, 那么自然就會有人問了, 為什么要使用 Mock 對象? 使用它有什么好處呢?
下面我們以一個簡單的例子來展示一下 Mock 對象到底有什么用.
假設我們正在編寫一個銀行的服務 BankService, 這個服務的依賴關系如下:
當我們需要測試 BankService 服務時, 該真么辦呢?
一種方法是構建真實的 BankDao, DB, AccountService 和 AuthService 實例, 然后注入到 BankService 中.
不用我說, 讀者們也肯定明白, 這是一種既笨重又繁瑣的方法, 完全不符合單元測試的精神. 那么還有一種更加優雅的方法嗎? 自然是有的, 那就是我們今天的主角Mock Object. 下面來看一下使用 Mock 對象后的框架圖:
我們看到, BankDao, AccountService 和 AuthService 都被我們使用了虛擬的對象(Mock 對象) 來替換了, 因此我們就可以對 BankService 進行測試, 而不需要關注它的復雜的依賴了.
Mockito 基本使用
為了簡潔期間, 下面的代碼都省略了靜態導入import static org.mockito.Mockito.*;
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 對象后, 我們可以定制它的具體的行為. 例如:
@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.
總結
以上是生活随笔為你收集整理的手把手教你 Mockito 的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 00-02.PHP 网站假设 之 学习P
- 下一篇: 目标与目标感