拓展阅读
mockito-02-springaop 整合遇到的问题,失效
现象
发现引入一个日志组件后,导致 mockito 测试失效了。
原因
日志组件中通过 aop 切面增强输出日志,mockito 也是这个原理。
所以二者存在冲突。
解决思路
1)将 mock 的数据设置到对应的 bean 中,解决 mock 失效问题。
2)将被 spring 代理的对象还原为原始对象,让 mockito 可以正常代理。
3) 通过 spring 官方的 @MockBean 注解
方式1-设置 mock 对象
测试类:
public class HomeControllerTest extends TestCase {
private MockMvc mockMvc;
@InjectMocks
private HomeController homeController;
@Mock
private UserService userService;
@Before
public void setUp(){
MockitoAnnotations.initMocks(this);
this.homeController = new HomeController();
this.mockMvc = MockMvcBuilders.standaloneSetup(this.homeController).build();
.....
}
Controller 类
public class HomeController {
@Autowired
private UserService userService;
.....
}
在这种情况下就会造成@Mock和@Autowired注入冲突,导致注入失败。
可以将测试类中加入反射注入即可:
@Before
public void setUp(){
MockitoAnnotations.initMocks(this);
this.homeController = new HomeController();
ReflectionTestUtils.setField(homeController,"userService",userService);
this.mockMvc = MockMvcBuilders.standaloneSetup(this.homeController).build();
}
即可解决注入失败问题。
方式 2
思路
我们通过把 spring 增强的代理恢复为普通对象,然后让 mockito 初始化处理。
测试例子
package com.github.houbb.dwz.server.service;
import com.github.houbb.dwz.server.BootApplication;
import com.github.houbb.dwz.server.service.biz.MyTestBiz;
import com.github.houbb.dwz.server.service.service.MyTestService;
import com.github.houbb.dwz.server.utils.MockitoProxyUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.Assert;
import static org.mockito.Mockito.*;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BootApplication.class)
public class MyTestBizTest {
@Mock
private MyTestService myTestService;
@Autowired
@InjectMocks
private MyTestBiz myTestBiz;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
}
@Test
public void queryByIdTest() {
when(myTestService.queryById("1")).thenReturn("mockit-id");
String result = myTestBiz.queryById("1");
assert result.equals("mockit-id");
}
}
这种,如果 service 被代理的话,会导致 mock 无效,断言无效。
解决方式
工具
@SuppressWarnings("all")
public static <T> T unWrapProxy(final T proxyObject) {
try {
if(AopUtils.isAopProxy(proxyObject)
&& proxyObject instanceof Advised) {
Advised advised = (Advised) proxyObject;
// 原始对象
return (T) advised.getTargetSource().getTarget();
}
return proxyObject;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
可以通过这个方法,把所有的 mock 对象恢复。
测试
import static org.mockito.Mockito.*;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BootApplication.class)
public class MyTestBizTest {
@Mock
private MyTestService myTestService;
@Autowired
@InjectMocks
private MyTestBiz myTestBiz;
@Before
public void init() {
myTestBiz = MockitoProxyUtils.unWrapProxy(myTestBiz);
myTestService = MockitoProxyUtils.unWrapProxy(myTestService);
MockitoAnnotations.initMocks(this);
}
}
init() 方法调整一下,测试就可以通过。
解决方式优化
不足:需要手动设置每一个字段,显然不够优雅。
改进思路:我们通过反射,将每一个属性还原。
方法
public static void unWrapAllFieldsProxy(final Object testInstance) {
try {
Field[] fields = testInstance.getClass().getDeclaredFields();
if(fields.length <= 0) {
return;
}
for(Field field : fields) {
// 跳过 final 字段
if(Modifier.isFinal(field.getModifiers())) {
continue;
}
field.setAccessible(true);
Object fieldVal = field.get(testInstance);
Object newFieldVal = unWrapProxy(fieldVal);
field.set(testInstance, newFieldVal);
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
测试
@Before
public void init() {
MockitoProxyUtils.unWrapAllFieldsProxy(this);
MockitoAnnotations.initMocks(this);
}
小结
这里可以发现需要修改 init 方法,最好是统一继承自抽象测试类。
参考资料
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.tracing
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing
Mockito fails to verify the second time on the proxied bean
Spring Test support for @MockBean or similar [SPR-14083]
Spring Test support for @MockBean or similar [SPR-14083]