什么是 Mockito
Mockito is a mocking framework, JAVA-based library that is used for effective unit testing of JAVA applications. Mockito is used to mock interfaces so that a dummy functionality can be added to a mock interface that can be used in unit testing.
Mockito 是一個模擬框架,可以有效地來進行 Java 單元測試鞠呈。Mockito 可以用來模擬接口晾蜘,使得在單元測試中可以使用一個虛構的方法蜀肘。
為什么需要模擬布朦?
單元測試的想法是我們要測試我們的代碼而不測試依賴煞烫。有時候我們不想依靠依賴,或者說依賴沒有準備好猴鲫,此時我們需要模擬对人。
基本用法
-
mock()
/@Mock
: 創(chuàng)建模擬- optionally specify how it should behave via Answer/MockSettings
-
when()
/given()
來指定模擬的行為(方法) - 默認情況下,調用 mock 對象的帶返回值的方法會返回默認的值拂共,比如返回
null
牺弄、0
值或者false
等。 - 相同的方法和參數唯一確認一個代理宜狐。比如你可以分別代理
get(int)
方法在參數分別為0
和1
時的不同行為势告。
spy()
/@Spy
: 實現部分模擬, 真正的方法會被調用,但是也可以被 stubbing 和 verify@InjectMocks
: 自動注入被@Spy
或@Mock
注解的屬性-
verify()
: 驗證方法是否被調用抚恒,調用了幾次- 可以使用靈活的匹配參數咱台,例如
any()
- 也可以通過
@Captor
來捕獲參數
- 可以使用靈活的匹配參數咱台,例如
具體參見:https://static.javadoc.io/org.mockito/mockito-core/2.22.0/org/mockito/Mockito.html
在這里通過 maven 進行構建:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.22.0</version>
<scope>test</scope>
</dependency>
</dependencies>
1. 使用 verify 來驗證行為,例如方法是否被調用
import org.junit.Test;
import java.util.List;
import static org.mockito.Mockito.*;
public class TestRunner {
@Test
public void Test1() {
// 模擬一個接口
List mockedList = mock(List.class);
// 使用模擬對象
mockedList.add("one");
mockedList.clear();
// 驗證行為俭驮,方法是否被調用
verify(mockedList).add("one");
verify(mockedList).clear();
}
}
2. 如何使用 stubbing 存根
@Test
public void Test2() {
// 不光可以模擬接口回溺,可以模擬一個實體類
LinkedList mockedList = mock(LinkedList.class);
// stubbing 存根
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());
// 打印 first
System.out.println(mockedList.get(0));
// 拋出 java.lang.RuntimeException
System.out.println(mockedList.get(1));
// 打印 "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));
}
3. 參數匹配
例如我們可以使用 anyInt()
來匹配任意的整數類型。
更多的內嵌 matcher 和自定義 matcher表鳍,請參見:https://static.javadoc.io/org.mockito/mockito-core/2.22.0/org/mockito/ArgumentMatchers.html
@Test
public void Test3() {
// 不光可以模擬接口馅而,可以模擬一個實體類
LinkedList mockedList = mock(LinkedList.class);
// stubbing 存根,使用內嵌的 anyInt() 來匹配參數
when(mockedList.get(anyInt())).thenReturn("element");
// 打印 element
System.out.println(mockedList.get(999));
// 驗證行為譬圣,方法是否被調用
verify(mockedList).get(anyInt());
}
4. 驗證方法被調用的次數
@Test
public void Test4() {
// 不光可以模擬接口瓮恭,可以模擬一個實體類
LinkedList mockedList = mock(LinkedList.class);
// 使用 mock
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice");
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");
// 驗證方法被調用過多少次
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");
// 驗證方法沒有被調用過
verify(mockedList, never()).add("never happened");
// 驗證方法被調用過多少次
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("three times");
verify(mockedList, atMost(5)).add("three times");
}
5. 驗證方法的調用順序
@Test
public void Test5() {
List singleMock = mock(List.class);
// 使用 mock
singleMock.add("was added first");
singleMock.add("was added second");
// 創(chuàng)建 InOrder
InOrder inOrder = inOrder(singleMock);
// 驗證先調用 "was added first",再調用 "was added second"
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");
}
6. 使用 @Mock 注解
- Minimizes repetitive mock creation code. 簡化 Mock 的創(chuàng)建
- Makes the test class more readable. 增加代碼的可讀性
- Makes the verification error easier to read because the field name is used to identify the mock.
@Mock List mockedList;
@Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
}
@Test
public void Test6() {
// 使用模擬對象
mockedList.add("one");
mockedList.clear();
// 驗證行為厘熟,方法是否被調用
verify(mockedList).add("one");
verify(mockedList).clear();
}
7. 使用 stubbing 存根模擬連續(xù)的調用
@Test
public void Test7() {
// 模擬一個接口
List mockedList = mock(List.class);
when(mockedList.get(anyInt()))
.thenThrow(new RuntimeException())
.thenReturn("foo");
// 第一次調用屯蹦,拋出異常
mockedList.get(1);
// 第二次調用,打印 foo
System.out.println(mockedList.get(1));
}
也可以這樣使用:
when(mock.someMethod("some arg")).thenReturn("one", "two", "three");
8. 使用帶有 callback 回調的 stubbing 存根
@Test
public void Test8() {
// 模擬一個接口
List mockedList = mock(List.class);
when(mockedList.get(anyInt())).thenAnswer(
new Answer() {
public Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
Object mock = invocation.getMock();
return "called with arguments: " + Arrays.toString(args);
}
});
// 打印 "called with arguments: [1]"
System.out.println(mockedList.get(1));
}
9. 使用 doReturn()绳姨,doThrow()登澜,doAnswer(),doNothing()飘庄,doCallRealMethod() 來 stub 空方法 void method
@Test
public void Test9() {
// 模擬一個接口
List mockedList = mock(List.class);
doThrow(new RuntimeException()).when(mockedList).clear();
// 拋出異常 RuntimeException:
mockedList.clear();
}
10. 在真正的對象上 spy
When you use the spy then the real methods are called (unless a method was stubbed).
當你使用 spy 的時候脑蠕,真正的對象上的方法會被調用,除非你使用了 stubbing跪削,例如 when()...
@Test
public void Test10() {
List list = new LinkedList();
List spy = spy(list);
//using the spy calls *real* methods
spy.add("one");
spy.add("two");
// 打印 one
System.out.println(spy.get(0));
// 打印 2
System.out.println(spy.size());
verify(spy).add("one");
verify(spy).add("two");
}
11. 實現局部模擬
@Test
public void Test11() {
// 模擬一個接口
List mockedList = mock(LinkedList.class);
// 調用實際的方法谴仙,實現局部模擬
when(mockedList.size()).thenCallRealMethod();
System.out.println(mockedList.size());
}
12. 重置 Mock
通過 reset(mock);
方法,來重置之前設置的 stubbing碾盐。
示例
假設我們要測試一個計算器程序 CalculatorApplication
晃跺,但是該程序依賴于 CalculatorService
實現具體的計算過程。
代碼如下:
public interface CalculatorService {
public double add(double input1, double input2);
public double subtract(double input1, double input2);
public double multiply(double input1, double input2);
public double divide(double input1, double input2);
}
public class CalculatorApplication {
private CalculatorService calcService;
public void setCalculatorService(CalculatorService calcService) {
this.calcService = calcService;
}
public double add(double input1, double input2) {
return calcService.add(input1, input2);
}
public double subtract(double input1, double input2) {
return calcService.subtract(input1, input2);
}
public double multiply(double input1, double input2) {
return calcService.multiply(input1, input2);
}
public double divide(double input1, double input2) {
return calcService.divide(input1, input2);
}
}
問題來了:在測試時毫玖,我們可能并沒有 CalculatorService
這個接口的具體實現類掀虎,例如 CalculatorServiceImpl
凌盯。
因此我們需要在測試時模擬 CalculatorService
這個接口的行為。
此時我們使用 mockito 來模擬行為烹玉。
mockito 可以通過注解的方式來使用:
-
@RunWith(MockitoJUnitRunner.class)
:指定 Test Runner -
@InjectMocks
:Mark a field on which injection should be performed. 標識一個變量驰怎,該變量會被注入一個 Mock。例如CalculatorApplication
會被注入一個CalculatorService
的實現二打。- 注意:
CalculatorApplication
中需要定義一個set
方法來注入砸西。
- 注意:
-
@Mock
:Mark a field as a mock. 標識一個變量,該變量會被 Mock址儒。例如CalculatorService
。- 在標記出 Mock 后衅疙,可以通過
when
來模擬該 Mock 的行為莲趣。
- 在標記出 Mock 后衅疙,可以通過
示例如下:
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class Mockito_Test {
@InjectMocks
CalculatorApplication calculatorApplication = new CalculatorApplication();
@Mock
CalculatorService calcService;
@Test
public void testAdd() {
// 模擬 CalculatorService 的行為
when(calcService.add(10.0, 20.0)).thenReturn(30.00);
// 測試
Assert.assertEquals(calculatorApplication.add(10.0, 20.0), 30.0, 0);
}
}
Mockito 原理
首先我們要知道,Mock 對象這件事情饱溢,本質上是一個 Proxy 模式的應用喧伞。
Proxy 模式說的是,在一個真實對象前面绩郎,提供一個 Proxy 對象潘鲫,所有對真實對象的調用,都先經過 Proxy 對象肋杖,然后由 Proxy 對象根據情況溉仑,決定相應的處理,它可以直接做一個自己的處理状植,也可以再調用真實對象對應的方法
Proxy 對象對調用者來說浊竟,可以是透明的,也可以是不透明的津畸。
Mockito 就是用 Java 提供的 Dynamic Proxy API 來實現的振定。
關于 Java 的動態(tài)代理,請參見 Java 動態(tài)代理
Mockito 本質上就是在代理對象調用方法前肉拓,用 Stubbing 的方式設置其返回值后频,然后在真實調用時,用代理對象返回預設的返回值暖途。
我們來看如下的代碼:
List mockedList = mock(List.class);
// 設置 mock 對象的行為 - 當調用其 get 方法獲取第 0 個元素時卑惜,返回 "first"
when(mockedList.get(0)).thenReturn("first");
Java 中的程序調用是以棧的形式實現的,對于 when()
方法丧肴,mockedList.get(0)
方法的調用對它是不可見的残揉。when()
能接收到的,只有 mockedList.get(0)
的返回值芋浮。
所以抱环,上面的代碼也等價于:
// stubbing 存根
Object ret = mockedList.get(0);
when(ret).thenReturn("first");
看看 when()
方法的源碼:
public <T> OngoingStubbing<T> when(T methodCall) {
MockingProgress mockingProgress = ThreadSafeMockingProgress.mockingProgress();
mockingProgress.stubbingStarted();
OngoingStubbing<T> stubbing = mockingProgress.pullOngoingStubbing();
if (stubbing == null) {
mockingProgress.reset();
throw Reporter.missingMethodInvocation();
} else {
return stubbing;
}
}
看看 OngoingStubbing
接口里有哪些方法:
public interface OngoingStubbing<T> {
OngoingStubbing<T> thenReturn(T var1);
OngoingStubbing<T> thenReturn(T var1, T... var2);
OngoingStubbing<T> thenThrow(Throwable... var1);
OngoingStubbing<T> thenThrow(Class<? extends Throwable> var1);
OngoingStubbing<T> thenThrow(Class<? extends Throwable> var1, Class... var2);
OngoingStubbing<T> thenCallRealMethod();
OngoingStubbing<T> thenAnswer(Answer<?> var1);
OngoingStubbing<T> then(Answer<?> var1);
<M> M getMock();
}
mock 對象所有的方法最終都會交由 MockHandlerImpl
的 handle
方法處理壳快,部分代碼如下:
OngoingStubbingImpl<T> ongoingStubbing = new OngoingStubbingImpl(this.invocationContainer);
ThreadSafeMockingProgress.mockingProgress().reportOngoingStubbing(ongoingStubbing);
StubbedInvocationMatcher stubbing = this.invocationContainer.findAnswerFor(invocation);
StubbingLookupNotifier.notifyStubbedAnswerLookup(invocation, stubbing, this.invocationContainer.getStubbingsAscending(), (CreationSettings)this.mockSettings);
Object ret;
if (stubbing != null) {
stubbing.captureArgumentsFrom(invocation);
try {
ret = stubbing.answer(invocation);
} finally {
ThreadSafeMockingProgress.mockingProgress().reportOngoingStubbing(ongoingStubbing);
}
return ret;
} else {
ret = this.mockSettings.getDefaultAnswer().answer(invocation);
DefaultAnswerValidator.validateReturnValueFor(invocation, ret);
this.invocationContainer.resetInvocationForPotentialStubbing(invocationMatcher);
return ret;
}
when
調用的基本形式是 when(mock.doSome())
,此時镇草,當 mock.doSome()
時即會觸發(fā)上面的語句眶痰,OngoingStubbingImpl
表示正在對一個方法打樁的包裝,invocationContainerImpl
相當于一個 mock 對象的管家梯啤,記錄著 mock 對象方法的調用竖伯。