Mockito 是Java中用於單元測試的模擬框架。html
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>LATEST</version>
</dependency>
複製代碼
經過class參數(類、接口)建立一個mock對象,該對象與真實建立的對象有所區別。 使用 Mockito 類的一系列靜態方法。java
public static <T> T mock(Class <T> classToMock) 複製代碼
Mockito 須要依賴Junit。bash
package org.byron4j.cookbook.mocketio.basic;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class MockitoAnnotationTest {
@Test
public void whenNotUseMockAnnotation_thenCorrect() {
// 建立一個mock出來的ArrayList對象
List mockList = Mockito.mock(ArrayList.class);
// 調用mock對象的方法
mockList.add("one");
//mockList.add("one");
// 獲取mock對象的實際方法,獲取size,結果爲0
System.out.println("mockList.size(): " + mockList.size());
// toString方法
System.out.println("mockList's toString is: " + mockList);
// 驗證mock對象mockList的add方法是否被調用了一次
Mockito.verify(mockList).add("one");
assertEquals(0, mockList.size());
// 當調用mockList.size()的時候,老是返回100
Mockito.when(mockList.size()).thenReturn(100);
assertEquals(100, mockList.size());
}
}
複製代碼
運行輸出結果爲:框架
mockList.size(): 0
mockList's toString is: Mock for ArrayList, hashCode: 409962262 複製代碼
List mockList = Mockito.mock(ArrayList.class);
建立一個mock出來的ArrayList對象mockListmockList.add("one");
mockList.size()
結果爲0,代表mockList.add("one");
代碼僅僅表示發生了add行爲自己,並不會對mockList的其餘行爲產生影響。Mockito.verify(mockList).add("one");
assertEquals(0, mockList.size());
複製代碼
Mockito.when(mockList.size()).thenReturn(100);
assertEquals(100, mockList.size());
複製代碼
驗證包含的行爲(方法)發生過一次(被調用一次),即verify(mock, times(1)),例如: verify(mock).someMethod("some arg");
。等效於 verify(mock, times(1)).someMethod("some arg");
單元測試
Mockito.verify(mockList).add("one");
複製代碼
等效於測試
Mockito.verify(mockList, Mockito.times(1)).add("one");
複製代碼
Mockito.times(1)
參數1表示指望執行的次數是1。spa
public static <T> T verify(T mock, VerificationMode mode);
複製代碼
Mockito.times(int wantedNumberOfInvocations) 能夠獲得一個VerificationMode對象,實際調用了
VerificationModeFactory.times(wantedNumberOfInvocations)
獲取到一個Times對象:new Times(wantedNumberOfInvocations)
,Times實現了VerificationMode接口。code
參數一: mock 對象,必須的xml
參數二: 驗證模式:times(x), atLeastOnce() 或者 never() 等;若是是times(1)則可忽略該參數htm
times方法調用棧以下:
org.mockito.Mockito#times(int wantedNumberOfInvocations)
org.mockito.internal.verification.VerificationModeFactory#times(int wantedNumberOfInvocations)
org.mockito.internal.verification.Times(int wantedNumberOfInvocations)
複製代碼
Mockito.when方法定義以下:
public static <T> OngoingStubbing<T> when(T methodCall) 複製代碼
when方法須要傳遞一個 mock對象的方法 的調用,例如本例中咱們傳遞了mock對象mockList的mockList.size()方法的調用。 when方法會留一份存根,在咱們但願模擬在特定狀況下返回特定的返回值時,回調用它。 簡單的意圖就是: 當x方法調用的時候,就返回y
。
示例:
when(mock.someMethod()).thenReturn(10);
: 調用方法時返回10
when(mock.someMethod(anyString())).thenReturn(10);
: 靈活參數
when(mock.someMethod("some arg")).thenThrow(new RuntimeException());
: 調用方法時,拋出一個異常
when(mock.someMethod("some arg")).thenThrow(new RuntimeException()).thenReturn("foo");
: 連續調用不一樣的行爲
when(mock.someMethod("some arg")).thenReturn("one", "two");
: 連續的存根,第一次調用返回"one",第二次以及以後的調用返回"two"
when(mock.someMethod("some arg")).thenReturn("one").thenReturn("two");
: 和上面一條等同效果
when(mock.someMethod("some arg")).thenThrow(new RuntimeException(), new NullPointerException();
: 連續存根,拋異常
@Test
public void whenTest() {
List mock = Mockito.mock(List.class);
Mockito.when(mock.size()).thenReturn(-1);
System.out.println("mock.size():" + mock.size());
// 連續存根
Mockito.when(mock.size()).thenReturn(1).thenReturn(2).thenReturn(3);
for(int i=1; i <= 5; i++){
System.out.println("=====連續存根方式1:=====: " + mock.size());
}
Mockito.when(mock.size()).thenReturn(1,2, 3);
for(int i=1; i <= 5; i++){
System.out.println("#####連續存根方式2:#####: " + mock.size());
}
// 模擬異常
Mockito.when(mock.size()).thenThrow(new RuntimeException(), new NullPointerException());
try{
mock.size();
}catch (Exception e){
System.out.println(e);
}
try{
mock.size();
}catch (Exception e){
System.out.println(e);
}
}
複製代碼
運行輸出:
mock.size():-1
=====連續存根方式1:=====: 1
=====連續存根方式1:=====: 2
=====連續存根方式1:=====: 3
=====連續存根方式1:=====: 3
=====連續存根方式1:=====: 3
#####連續存根方式2:#####: 1
#####連續存根方式2:#####: 2
#####連續存根方式2:#####: 3
#####連續存根方式2:#####: 3
#####連續存根方式2:#####: 3
java.lang.RuntimeException
java.lang.NullPointerException
複製代碼
使用 @RunWith(MockitoJUnitRunner.class)
在類上開啓Mockito註解功能。
@RunWith(MockitoJUnitRunner.class)
public class MockitoAnnotationStartup {
}
複製代碼
經過 @Mock註解能夠獲得mock對象,等價於 Mockito.mock(class)。
/**註解獲得的mock對象*/
@Mock
List<String> mockList;
等價於
List<String> mock = Mockito.mock(List.class);
複製代碼
示例以下:
package org.byron4j.cookbook.mocketio.basic;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class MockitoAnnoTest extends MockitoAnnotationStartup{
/**註解獲得的mock對象*/
@Mock
List<String> mockList;
@Test
public void testRaw(){
List<String> mock = Mockito.mock(List.class);
mock.add("one");
mock.add("one");
Mockito.verify(mock, Mockito.times(2)).add("one");
Mockito.when(mock.size()).thenReturn(100);
assertEquals(100, mock.size());
}
@Test
public void testAnno(){
mockList.add("one");
mockList.add("one");
Mockito.verify(mockList, Mockito.times(2)).add("one");
Mockito.when(mockList.size()).thenReturn(100);
assertEquals(100, mockList.size());
}
}
複製代碼
和 @Mock 註解相似,還有 @Spy 註解。spy是密探間諜的意思,假冒的。
List<String> mock = Mockito.spy(List.class);
複製代碼
使用註解
@Spy
List<String> spyList;
複製代碼
參數捕獲器 ArgumentCaptor 對應註解 @Captor。
原始方式建立一個參數捕獲器:
@Test
public void whenNotUseCaptorAnnotation_thenCorrect() {
List mockList = Mockito.mock(List.class);
ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);
mockList.add("one");
Mockito.verify(mockList).add(arg.capture());
assertEquals("one", arg.getValue());
}
複製代碼
使用@Captor註解建立一個參數捕獲器:
@Mock
List mockedList;
@Captor
ArgumentCaptor argCaptor;
@Test
public void whenUseCaptorAnnotation_thenTheSam() {
mockedList.add("one");
Mockito.verify(mockedList).add(argCaptor.capture());
assertEquals("one", argCaptor.getValue());
}
複製代碼
ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);
: 建立一個參數捕獲器Mockito.verify(mockedList).add(argCaptor.capture());
: 在一個驗證中使用捕獲器捕獲方法add的參數; capture 方法必須在一個驗證中。argCaptor.getValue()
: 獲取參數捕獲器捕獲到的參數@InjectMocks
註解能夠將mock的屬性自動注入到測試對象中。
@Mock
Map<String, String> wordMap;
@InjectMocks
MyDictionary myDictionary = new MyDictionary();
@Test
public void testInjectMocks(){
Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning");
assertEquals("aMeaning", myDictionary.getMeaning("aWord"));
System.out.println(myDictionary.getMeaning("aWord"));
}
class MyDictionary{
Map<String, String> wordMap;
public String getMeaning(String word){
return wordMap.get(word);
}
}
複製代碼
Map<String, String> wordMap;
@Mock
Map<String, String> wordMap;
複製代碼
@InjectMocks
MyDictionary myDictionary = new MyDictionary();
複製代碼
則會將mock出來的對象wordMap注入到myDictionary實例的同名屬性中。
注意事項:
使用註解最小化重複編寫建立mock對象的代碼
使用註解讓測試案例可讀性更好
使用 @InjectMocks 註解注入 @Spy 和 @Mock 對象
參考資料: