Mockito打樁測試常用的幾個接口



文章目錄
一站式講明白Mockito打樁測試常用的幾個接口
1. 幾個術語:
2. Mock對象和Mock部分對象(partial-mock)
3. 否真正執行方法,doXxx和thenXxx
4. Mock方法的返回值,doReturn 和 thenReturn
5. Mock方法,根據不同入參返回不同處理邏輯,thenAnswer和doAnswer
6. 設置Mock的對象的屬性

本文不求講的全面,只求講的實用,拿來就能用。另外本文沒有涉及到異常相關的打樁,下次再續。
1. 幾個術語:

  • 真實對象:代碼中定義的class,並使用new方法初始化的對象
  • mock對象:模擬真實對象的對象,采用Mockito初始化的對象
  • 真實方法:class中定義的方法
  • mock方法:采用Mockito定義的方法,測試時用來替代真實方法

 

打樁:Stub,就是把所需要的測試數據塞到一個對象里,重點關注測試目標的方法,對於不易構造或者不易獲取對象和方法都采用樁來代替,這個過程就叫打樁。Mockito的doXxx方法返回值就是Stubber類型。

2. Mock對象和Mock部分對象(partial-mock)
場景
Mock對象的場景是:測試過程中,對於某些不容易構造或者不容易獲取的對象,就可以用一個虛擬的對象來創建以便測試。
Mock部分對象的場景:希望調用部分mock的方法,其余的方法調用真實的方法。

接口
// mock整個對象,對函數掉調用都使用mock的方法,除非顯示的調用doCallRealMethod()
Mockito.mock(Class<T> classToMock, MockSettings mockSettings)
//mock部分對象,對函數的調用均執行真正的方法,除了使用doXxx或者thenXxx的部分。
Mockito.spy(Class<T> classToSpy)

實例
import org.mockito.Mockito;

// 默認就執行mock方法
JobManager jobManager = Mockito.mock(JobManager.class);
// 除了在測試用例中打樁mock的方法以外,其他都執行真實的方法
JobManager jobManager = Mockito.spy(JobManager.class);

3. 否真正執行方法,doXxx和thenXxx
場景
doXxx的接口都是不會執行對象的真實方法,直接執行doXxx中的方法/值/異常。
thenXxx的接口都是會先執行對象的真實方法,然后返回thenXxx中的處理邏輯/值/異常。
接口
Mockito.doReturn(Object toBeReturned)
Mockito.doAnswer(Answer answer)
Mockito.doThrow(Throwable... toBeThrown)
......
thenReturn(T var1)
thenAnswer(Answer<?> var1)
thenThrow(Throwable... var1)
......

實例
// doXxx公式:doXxx(返回值/方法).when(mock對象).對象的方法(入參的類型),
// doXxx如果沒有邏輯也可以doNothing,例如:
Mockito.doReturn(response).when(jobManager).queryUser(any(User.class));
// thenXxx公式:when(mock對象.方法).thenXxx(返回值/邏輯),例如
Mockito.when(jobManager.queryUser(any(User.class))).thenReturn(user1);

4. Mock方法的返回值,doReturn 和 thenReturn
場景
doReturn或者thenReturn一般是直接給一個方法的返回值(不是一段代碼邏輯)。

接口
Mockito.doReturn(Object toBeReturned)
Mockito.doReturn(Object toBeReturned, Object... toBeReturnedNext)
thenReturn(T var1);
thenReturn(T var1, T... var2);

實例
// 調用jobManager的queryUser方法時,不論入參是多少,直接返回"user0"
Mockito.doReturn("user0").when(jobManager).queryUser(any(User.class));
// 調用jobManager的queryUser方法時,正常調用該類的真實方法
Mockito.when(jobManager.queryUser(any(User.class))).thenReturn("user0");

// doReturn 和thenReturn可以支持按照調用次數返回多個不同的值,比如第一次調用返回user0,第二次返回user1,如下:
Mockito.doReturn("user0","user1", "user2").when(jobManager).queryUser(any(User.class));
Mockito.when(jobManager.queryUser(any(User.class))).thenReturn("user0","user1", "user2");

5. Mock方法,根據不同入參返回不同處理邏輯,thenAnswer和doAnswer
場景
某些方法沒有一個固定的返回值,需要根據入參的具體參數值,執行一些代碼邏輯就用thenAnswer或者doAnswer。
同樣,使用doAnswer的接口都是不會執行對象的真實方法,直接執行doAnswer的定義的方法。
使用thenAnswer的接口會先執行真實方法,然后調用thenAnswer中定義的方法進行返回。

接口
Mockito.doAnswer(Answer answer)
thenAnswer(Answer<?> var1)

實例
mock一個根據userId獲取firstTask的方法,假設真實方法是去數據庫中查詢,那么此處就可以根據userId直接返回一個task對象。

import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
......
Map<String, UserInfo> userInfos=new HashMap<>();
Map<String, TaskInfo> taskInfos = new HashMap<>();
......
Mockito.doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
// 只有一個參數
String userId = invocation.getArgument(0);
LOGGER.info("userId:" + userId);
UserInfo userInfo = userInfos.get(userId);
String userName = userInfo.getName();
List<TaskInfo> firstTaskInfos = new ArrayList<>();
TaskInfo taskInfo;
switch (userName) {
case "case1":
taskInfo = taskInfos.get("case1");
break;
case "case2":
taskInfo = taskInfos.get("case2");
break;
case "case3":
taskInfo = taskInfos.get("case3");
break;
default:
throw new IllegalStateException("Unexpected value: " + userName);
}
firstTaskInfos.add(taskInfo);
return beginTaskInfos;
}
}).when(taskInfoService).getfirstTaskInfosByuserId(anyString());

對於thenAnswer中的Answer實現方法和doAnswer是類似的,和doAnswer的區別仍然是doAnswer不需要執行真實方法,直接執行mock方法。thenAnswer是先執行真實方法,然后再執行Answer中定義的方法。

6. 設置Mock的對象的屬性
場景
我遇到的場景是在spring框架下,設置Mock對象不會自動初始化@AutoWired的屬性,此時就需要顯式的設置Mock對象中需要用到的屬性。 其原理是用反射機制來設置的。

接口和實例
spring框架中使用如下接口,此方法可以用來設置private的屬性:

ReflectionTestUtils.setField(Object targetObject, String name, @Nullable Object value)
1
或者

Field field = ReflectionUtils.findField(targetClass, name, type);
if (field == null) {
....
}
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, targetObject, value);

如果是非spring框架,也可以直接使用Java的反射機制:

import java.lang.reflect.Field;
......
Field field = target.getClass().getDeclaredField(fieldName);
field.setAccessible(true); //改成可訪問,不管現有修飾
field.set(target, value);

實例
參考資料:

https://www.journaldev.com/21816/mockito-tutorial
Mockito源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM