拓展阅读

jmockit-01-jmockit 入门使用案例

jmockit-02-概览

jmockit-03-Mocking 模拟

jmockit-04-Faking 伪造

jmockit-05-代码覆盖率

mockito-01-入门介绍

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]

Mocks are not injected in Spring AOP proxies

当Mock注解和Spring注解冲突时