文章目錄
- 1. 無法 spy 或者 mock final 類
- 2. doReturn when 與 when().thenReturn()
- 3. doReturn 仍然產生真實的呼叫
- 4. doReturn()未生效,但執行物件是打樁的 mock/spy物件
- 5. 嵌套服務的測驗
總結使用Mockito遇到的問題
1. 無法 spy 或者 mock final 類
升級 mockito的版本
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.3.3</version>
</dependency>
2. doReturn when 與 when().thenReturn()
thenReturn()之前的方法呼叫是在when條件中;關于兩者的區別,見doReturn與 thenReturn的區別
AnyClass mock = mock(AnyClass.class);
doReturn(true).when(mock).hasReturnMethod();
when(mcok.hasReturnMethod()).thenReturn()
3. doReturn 仍然產生真實的呼叫
debug進去檢查執行真實方法的物件是否是你mock/spy的物件;如果不是,可能是代理的原因;比如使用的是 Spring 框架,你的某些bean 已經被 SpringAop加強了,當你從 applicationContext 中 獲取到要測驗的 serviceBean;
// 獲取要測驗的服務
AccountServcie accountServiceProxy = applicationContext.getBean(AccountServcie.class);
// 監控
AccountServcie spy = spy(accountServiceProxy);
// 打樁待測驗方法,目標無論如何怎么呼叫,都回傳余額 1
doReturn(1).when(spy).queryBalance(anyString());
// 使用 spy 呼叫待測驗方法
spy.queryBalance("CHY");
上述代碼執行時發現真的呼叫了 accountService 的查詢介面,而不像 doReturn() 說的沒有副作用;問題可能原因是 spy的accountService是一個被SpringAop的一個代理類;當代碼運行時,執行遠程呼叫的是 accountServiceProxy 的 target accountService,這個 accountService 并沒有被 spy;
若上述場景和你一致,可參考這篇博客, 獲取springAop代理物件的真實物件
或者參考這一方法
public class ProxyUtils {
/**
* 獲取 目標物件
*
* @param proxy 代理物件
* @return 目標物件
* @throws Exception 任何例外
*/
public static Object getTarget(Object proxy) throws Exception {
if (!AopUtils.isAopProxy(proxy)) {
return proxy;
}
AdvisedSupport closeTargetProxy = getCloseTargetProxy(proxy);
return closeTargetProxy.getTargetSource().getTarget();
}
/**
* 替換目標物件
*
* @param proxy 代理物件
* @param anotherTarget 用于代替目標物件的物件
* @throws Exception 任何例外
*/
public static void replaceTarget(Object proxy, Object anotherTarget) throws Exception {
if (!AopUtils.isAopProxy(proxy)) {
throw new IllegalArgumentException("無法給一個非代理物件替換真實的目標物件");
}
// 找到離真實物件最近的建言
AdvisedSupport advisedSupport = getCloseTargetProxy(proxy);
advisedSupport.setTarget(anotherTarget);
}
/**
* 獲取離真實物件最近的那一個代理
*
* @param proxy 代理
* @return 真實物件最近的代理
* @throws Exception 任意例外
*/
private static AdvisedSupport getCloseTargetProxy(Object proxy) throws Exception {
if (!AopUtils.isAopProxy(proxy)) {
throw new IllegalArgumentException("無法給一個非代理物件替換真實的目標物件");
}
if (AopUtils.isJdkDynamicProxy(proxy)) {
return getJdkDynamicProxyCloseTargetObject(proxy);
} else {
return getCglibProxyCloseTargetObject(proxy);
}
}
private static AdvisedSupport getJdkDynamicProxyCloseTargetObject(Object proxy) throws Exception {
Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
h.setAccessible(true);
AopProxy aopProxy = (AopProxy) h.get(proxy);
Field advised = aopProxy.getClass().getDeclaredField("advised");
return findAdvisedSupport(advised, aopProxy);
}
private static AdvisedSupport getCglibProxyCloseTargetObject(Object proxy) throws Exception {
Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
h.setAccessible(true);
Object dynamicAdvisedInterceptor = h.get(proxy);
Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
return findAdvisedSupport(advised, dynamicAdvisedInterceptor);
}
private static AdvisedSupport findAdvisedSupport(Field advised, Object interceptorOrProxy) throws Exception {
advised.setAccessible(true);
AdvisedSupport advisedSupport = (AdvisedSupport) advised.get(interceptorOrProxy);
Object target = advisedSupport.getTargetSource().getTarget();
if (!AopUtils.isAopProxy(target)) {
return advisedSupport;
} else {
return getCloseTargetProxy(target);
}
}
4. doReturn()未生效,但執行物件是打樁的 mock/spy物件
依然以 doReturn 仍產生真實呼叫的偽代碼為例;
// 獲取要測驗的服務
AccountServcie accountServiceProxy = applicationContext.getBean(AccountServcie.class);
// 監控
AccountServcie spy = spy(accountServiceProxy);
// 打樁待測驗方法,目標無論如何怎么呼叫,都回傳余額 1
doReturn(1).when(spy).queryBalance(anyString());
// 使用 spy 呼叫待測驗方法
spy.queryBalance(null);
兩者的唯一區別在于呼叫打樁方法的時候入參一個是 “CHY”,而一個是null;是的,就是引數沒有匹配到Answer導致的;如果你測驗方法的入參為null型別,使用 any(),而非 any(T.class),因為any(T.class) 是排除null的;
5. 嵌套服務的測驗
有些時候待測驗的方法依賴于別的服務提供的方法,而又沒有將方法拆開來寫;可以進行反射設定外部服務為 mock服務,對mock進行打樁測驗;切記測驗完畢后,需要將原物件設定回去;如代碼示意
@Service
public class AccountServcie implements IAccountService{
@Autowired
public RpcServcie rpcService;
@Cacheable(***)
public Money queryBalance(@NonNull String name,@Nullable String code){
queryBalanceByRpc(name,code)
// doSomething
return Money;
}
private Money queryBalanceByRpc(@NonNull String name,@Nullable String code){
rpcService.queryBalance(name,code);
// doSomething
return Money;
}
}
public interface RpcService{
/**
* 名字必須,不提供幣種回傳默認幣種余額
*/
ReponseMessage queryBalance(@NonNull String name,@Nullable String code);
}
public class Test{
@Test
public void queryBalanceTets(){
AccountServcie accountServcieProxy = context.getBean(AccountServcie.class);
RpcService originService = accountServcieProxy.rpcService;
RpcService mock = mock(RpcService.class);
// 打樁
doReturn(new Money()).when(mcok).queryBalance(anyString(),any());
// 反射設定值(這里為了表達方便設定域屬性為public)
accountServcieProxy.rpcService = mock ;
// 呼叫
accountServcieProxy.queryBalance("tom",null);
// 還原
accountServcieProxy.rpcService = originService ;
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/295384.html
標籤:其他
上一篇:flutter入坑,Expanded child isn‘t defined
下一篇:發送短信打開帶引數小程式
