1. 原理介绍

通过BeanFactoryPostProcessor向BeanFactory中注册需要进行Mock的对象,使当前Bean容器在依赖注入时使用
我们提供的Mock对象注入到实例中使用。
具体需要交给容器管理的mock实例,是通过TestExecutionListener在容器开始启动前去解析当前测试类中的使用@Mock
注解的字段,然后根据类型创建对应的Mock实例,将创建出来的Mock实例通过BeanFactoryPostProcessor注册到容器中,
以供依赖注入使用。

2.代码实现

注册Mock实例部分
public class MockitoBeansPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Map<Class<?>, MockitoBeansTestExecutionListener.MockBeanWrapper> allMockBeans = MockitoBeansTestExecutionListener.resolvedAllMockBeans();
        for (Map.Entry<Class<?>, MockitoBeansTestExecutionListener.MockBeanWrapper> mockBeanWrapperEntry : allMockBeans.entrySet()) {
            beanFactory.registerResolvableDependency(mockBeanWrapperEntry.getKey(), mockBeanWrapperEntry.getValue().getMockObject());
        }
    }

}
解析@Mock注解部分
public class MockitoBeansTestExecutionListener extends AbstractTestExecutionListener {

    private static final Map<Class<?>, MockBeanWrapper> mockBeans = new ConcurrentHashMap<>(30);
    private static final Map<Class<?>, List<Field>> injectMockBeans = new ConcurrentHashMap<>(30);
    private static boolean hasInitialized = false;

    public static Map<Class<?>, MockBeanWrapper> resolvedAllMockBeans() {
        Assert.isTrue(hasInitialized);
        return Collections.unmodifiableMap(mockBeans);
    }

    @Override
    public void beforeTestClass(TestContext testContext) throws Exception {
        Field[] declaredFields = testContext.getTestClass().getDeclaredFields();
        //将需要mock的对象创建出来
        for (Field field : declaredFields) {
            Mock mockAnnon = field.getAnnotation(Mock.class);
            if (mockAnnon != null) {
                MockBeanWrapper wrapper = new MockBeanWrapper();
                Class<?> type = field.getType();
                wrapper.setMockObject(Mockito.mock(type));
                wrapper.setBeanType(type);
                wrapper.setBeanName(field.getName());
                mockBeans.putIfAbsent(wrapper.getBeanType(), wrapper);
                injectMockBeans.compute(testContext.getTestClass(), (targetClass, waitInjectFields) -> {
                    if (waitInjectFields == null) {
                        waitInjectFields = new ArrayList<>();
                    }
                    waitInjectFields.add(field);
                    return waitInjectFields;
                });
            }
        }
        hasInitialized = true;
    }

    @Override
    public void beforeTestMethod(TestContext testContext) throws Exception {
        Object testInstance = testContext.getTestInstance();
        List<Field> fields = injectMockBeans.get(testContext.getTestClass());
        if (fields != null) {
            for (Field field : fields) {
                field.setAccessible(true);
                field.set(testInstance, mockBeans.get(field.getType()).getMockObject());
            }
        }
    }

    public class MockBeanWrapper {

        private String beanName;
        private Class<?> beanType;
        private Object mockObject;

        public String getBeanName() {
            return beanName;
        }

        public void setBeanName(String beanName) {
            this.beanName = beanName;
        }

        public Class<?> getBeanType() {
            return beanType;
        }

        public void setBeanType(Class<?> beanType) {
            this.beanType = beanType;
        }

        public Object getMockObject() {
            return mockObject;
        }

        public void setMockObject(Object mockObject) {
            this.mockObject = mockObject;
        }
    }
}

3.使用Demo

MessageSupplier是将要进行Mock的接口
public interface MessageSupplier {
    String getMessage();
}
这个是依赖MessageSupplier的实例类
@Service
public class SomeService {

    @Autowired
    MessageSupplier messageSupplier;

    public void printMessage() {
        System.out.println(messageSupplier.getMessage());
    }
}
单元测试类
@TestExecutionListeners({MockitoBeansTestExecutionListener.class})
@ContextConfiguration(classes = {SimpleTestCase.class})
@ComponentScan(basePackageClasses = {SimpleTestCase.class})
public class SimpleTestCase extends AbstractJUnit4SpringContextTests {

    @Autowired
    private SomeService someService;
    @Mock
    MessageSupplier messageSupplier;
    @Test
    public void test() {

        doReturn("this is mock message.")
                .when(messageSupplier)
                .getMessage();
        someService.printMessage(); //输出this is mock message.
    }
    @Bean
    public BeanFactoryPostProcessor mockBeansPostProcessor(){
        return new MockitoBeansPostProcessor();
    }

}

4.总结

在使用微服务的系统架构中,做一次单元测试会比较麻烦,可能需要启N多关联服务或者去连接N多关联服务。
这就使得单元测试很难实行,在这种情况下可以通过上面的方法将在本模块中不存在的实例都通过Mock实例
使用,这样使用Mockito中的doReturn等方法来模拟输入,去测试相关的代码片段。
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐