Mockito
1 Overview
2 Maven 項目初始化
3 示例
3.1 第一個示例
3.2 自動 Mock
3.3 Mock 返回值
3.4 Mock 參數
3.5 自動注入 Mock 對象
3.6 驗證調用次數
3.7 預設 Exception
3.8 Void Mock
3.9 級聯 Mock
3.10 部分 Mock
4 FAQ
4.1 注意點
5 References
1 Overview
Mockito 是 Java 中用於 Mock 的一個開源項目。
Mock 用於如下目的
- 如果依賴的外部系統在測試環境下不可用,使用 Mock 跳過外部系統調用並模擬返回結果
- 驗證是否的確調用了 Mock 對象
2 Maven 項目初始化
-
創建 Maven Quick Start 項目
-
pom.xml 修改如下
3 示例
3.1 第一個示例
我們先看如下代碼
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import java.util.List; import org.junit.Test; public class FirstTest { @Test public void verifyBehavior() { @SuppressWarnings("unchecked") List<Integer> mock = mock(List.class); mock.add(1); mock.clear(); verify(mock).add(1); verify(mock).clear(); } }
作為第一個示例,我在此詳細解釋一下
首先,我們使用 Mockito.mock() 方來來生成 Mock 對象。這里我們需要注意兩點:
- 可以直接生成接口的 Mock 對象
- 范型對象 Mock
然后我們調用了 Mock 對象的兩個成員方法,在此我們需要更深刻地理解一下 Mock 對象的行為,如果我們在調用 add 方法后打印它的 size,我們會發現結果是 0 而不是 1。也就是說,Mock 對象只是攔截了所有對原始方法的調用並返回對應返回的類型的默認值,而不是真正地實現了這個接口或創建了對象實例。
然后是兩個 verify 方法,表示驗證 Mock 對象是否調用了對應的方法。注意驗證 add 調用時,可以驗證輸入參數(本例為 1),如果不想驗證,只需要確定是否調用,可以使用如下方式驗證
import static org.mockito.Matchers.anyInt; ... verify(mock).add(anyInt());
3.2 自動 Mock
可以使用如下方式自動生成 Mock 對象
import org.junit.Before; import org.mockito.Mock; import org.mockito.MockitoAnnotations; ... @Mock private List<Integer> mock; @Before public void setup() { MockitoAnnotations.initMocks(this); }
3.3 Mock 返回值
下例 mock一個Iterator類,預設當iterator調用next()時第一次返回hello,以后每次都返回world
import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.Iterator; import org.junit.Test; public class MockReturnTest { @Test public void verifyBehavior1() { @SuppressWarnings("unchecked") Iterator<String> iterator = mock(Iterator.class); when(iterator.next()).thenReturn("hello").thenReturn("world"); String result = iterator.next() + " " + iterator.next() + " " + iterator.next(); assertEquals("hello world world", result); } }
3.4 Mock 參數
可以使用以下 Mockito 對象來模擬任意輸入值
import static org.mockito.Matchers.any*
例如 anyString, anyInteger, anyChar 等,也可以使用 any() 方法來生成任意對象,例如
List<String> mock = mock(List.class); mock.add(any(String.class));
或者使用更簡單的 Mockito.any(), 如下所示
import static org.mockito.Mockito.any; List<String> mock = mock(List.class); mock.add(any());
3.5 自動注入 Mock 對象
對於如下的情況,我們需要 Mock 某對象的內部方法,如下所示,我們需要 Mock MainServer 內部的 OtherService:
常規情況下,我們需要手工注入 Mock 對象,如下所示:
PS:需要在 MainService 類中添加 setOtherService() 方法以允許修改 otherService
其中 DemoConfig 是配置類,對於 Spring Boot 框架,Test 類不會自動注入 Autowired 對象,需要使用 Config 類指定加載類,內容如下:
但更合理的方式是使用 @InjectMocks 注解來自動注入,如下所示
注意如下幾點:
- 在 @Before 方法中初始化 Mock 對象及自動注入
- 在需要自動注入成員的類上添加 @InjectMocks 注解
另外值得注意的是,@InjectMocks 只會注入當前對象的成員,不會遞歸深度注入對象,例如,我們如果將 MainService 修改如下:
添加 MiddleService 如下所示
這樣的話,Unit Test 需要修改如下:
3.6 驗證調用次數
如下代碼驗證了 add 方法需要被調用兩次
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import java.util.List; import org.junit.Test; public class CallTimesTest { @Test public void verifyBehavior1() { List<Integer> mock = mock(List.class); mock.add(1); mock.add(1); verify(mock, times(2)).add(1); } }
3.7 預設 Exception
下面代碼演示了預設 Exceptio 發生
import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import java.io.IOException; import java.io.OutputStream; import org.junit.Test; public class ExceptionTest { @Test(expected = IOException.class) public void when_thenThrow() throws IOException { OutputStream outputStream = mock(OutputStream.class); doThrow(new IOException()).when(outputStream).close(); outputStream.close(); } }
代碼說明如下:
- @Test(expected = IOException.class) 表示該測試需要有 IOException 拋出
- doThrow 表示指定操作將拋出指定異常
3.8 Void Mock
前面的 thenReturn 只適用於有返回值的方法,本例講述如何 Mock void 方法
聲明服務類如下
package com.lld.test.mockito; import org.springframework.stereotype.Component; @Component public class VoidService { public void sayHi(String name) { System.out.println("Hello, " + name); } }
測試類如下所示
package com.lld.test.mockito; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = DemoConfig.class) public class VoidTest { @Mock VoidService voidService; @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); } @Test public void voidTest() { doNothing().when(voidService).sayHi(anyString()); voidService.sayHi("Lindong"); verify(voidService, times(1)).sayHi(anyString()); } @Test public void voidArgumentTest() { ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class); doNothing().when(voidService).sayHi(argumentCaptor.capture()); voidService.sayHi("Lindong"); assertEquals("Lindong", argumentCaptor.getValue()); } @Test public void answerTest() { doAnswer(answer -> { String name = answer.getArgumentAt(0, String.class); System.out.println("invoke VoidService with argument: " + name); return null; }).when(voidService).sayHi(anyString()); voidService.sayHi("Lindong"); } }
代碼說明如下
- voidTest 演示了如何簡單地 Mock void 方法
- voidArgumentTest 演示了如何獲取 void 方法的參數
- answerTest 演示了如何截獲 void 調用
3.9 級聯 Mock
本例演示了如何自動 Mock 所有對象下的子對象
import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.junit.Test; public class DeepMockTest { @Test public void deepstubsAutoTest() { Account account = mock(Account.class, RETURNS_DEEP_STUBS); when(account.getRailwayTicket().getDestination()).thenReturn("Beijing"); account.getRailwayTicket().getDestination(); verify(account.getRailwayTicket()).getDestination(); assertEquals("Beijing", account.getRailwayTicket().getDestination()); } @Test public void deepstubsManualTest() { Account account = mock(Account.class); RailwayTicket railwayTicket = mock(RailwayTicket.class); when(account.getRailwayTicket()).thenReturn(railwayTicket); when(railwayTicket.getDestination()).thenReturn("Beijing"); account.getRailwayTicket().getDestination(); verify(account.getRailwayTicket()).getDestination(); assertEquals("Beijing", account.getRailwayTicket().getDestination()); } public class RailwayTicket { private String destination; public String getDestination() { return destination; } public void setDestination(String destination) { this.destination = destination; } } public class Account { private RailwayTicket railwayTicket; public RailwayTicket getRailwayTicket() { return railwayTicket; } public void setRailwayTicket(RailwayTicket railwayTicket) { this.railwayTicket = railwayTicket; } } }
代碼說明如下:
- deepstubsAutoTest 演示了自動創建子對象的 Mock (推薦)
- deepstubsManualTest 演示了手動創建子對象的 Mock
3.10 部分 Mock
如下例所示,我們需要使用 Mock 跳過 Exception
import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.spy; import org.junit.Test; public class PartialMockTest { @Test public void partialMockTest() throws Exception { TestObj mockObj = spy(new TestObj()); doNothing().when(mockObj).m1(); mockObj.m3(); } class TestObj { public void m1() throws Exception { throw new Exception("exception"); } public void m2() { System.out.println("m2 is invoked"); } public void m3() throws Exception { m1(); m2(); } } }
我們使用了 spy 方法,它返回的對象是一個真實的對象,所有的方法調用也都是真的方法調用。但像例子中演示的,可以 Mock 掉指定的方法。如果有返回值,也可以和以前的例子一樣使用 thenReturn。
4 FAQ
4.1 注意點
-
對於 @Mock 標注,MockitoAnnotations.initMocks(this); 一定要放在第一行
-
Mock 對象的所有方法均為假方法,而不是默認實現