资讯专栏INFORMATION COLUMN

使用Mockito修改Bean的依赖

Elle / 2789人阅读

摘要:概述在使用单元测试时经常会遇到某些依赖了外部资源,或者想主动绕过真正的方法执行返回结果而快速得到单元测试最终的期望结果,可能有以下两种场景,对于,设单元测试的方法是的方法和方法,在执行和方法时都会调用的不同方法,即依赖了一个场景是完全对进行

概述

在使用单元测试时经常会遇到某些dependency依赖了外部资源,或者想主动绕过真正的方法执行mock返回结果而快速得到单元测试最终的期望结果,可能有以下两种场景,
对于TestCase A,设单元测试的方法是Service A的execute1方法和execute2方法,在执行execute1和execute2方法时都会调用ServiceB的不同方法,即ServiceA依赖了ServiceB;一个场景是完全对ServiceB进行Mock,如单元测试ServiceA#execute1方法时都通过Mock返回结果;一个场景是部分ServiceB的方法执行真实的业务逻辑(如查询数据库),一部分方法执行Mock返回结果,或Spy,如如单元测试ServiceA#execute2方法时,只mock ServiceB#b2结果,真正执行ServiceB#b1方法。

对TestCase的Service的依赖Bean的完全Mock

当对ServiceA的方法执行单元测试时,如ServiceA -> ServiceB,此时对ServiceB进行Mock,然后将其设置到ServiceA的属性中;后续ServiceA调用ServiceB的方法都降得到Mock后的结果;而对于ServiceB对象的本来的依赖本案暂且将其忽略,后续改进;

思路是在TestCase中依赖ServiceA的同时标示Mock ServiceB,待TestCase依赖注入完成后,新建ServiceB的Mock对象替换ServiceA中的ServiceB依赖;

@TestExecutionListeners({MockitoDependencyInjectionTestExecutionListener.class})
public class AServiceMockTest extends BaseTest {
    @Mock
    private BService bservice;
    @Autowired
    private AService aservice;
    @Before
    public void setup(){
        doReturn("mock").when(bservice).b1();
    }
    @Test
    public void test() {
        a.execute1();
    }
}
@Service
public class AServiceImpl implements AService {
    @Autowired
    private BService bservice;
     
    @Override
    public String execute1() {
        return bservice.b1(); //will return mock after Mock
    }
}

当a.execute()执行时将调用aservice的属性bservice的b1方法,返回结果就是在setup方法中指定的结果;

监听TestCase的Service的依赖Bean

当对ServiceA进行单元测试时,依赖了ServiceB,需要获取ServiceB的b1方法的真正执行结果,Mock b2方法的结果,此时可以采用Spy方式;由于ServiceA依赖了ServiceB,而这个属性可能是个AopProxy对象,并不能直接使用Mockito.mock(bservice)或者Mockito.spy(bservice),所以这里@Spy注解指定的是实现类,通过MockitoDependencyInjectionTestExecutionListener处理后,获得一个Spy对象,同时这个Spy对象设置到bservice(AopProxy对象)中去;

@TestExecutionListeners({MockitoDependencyInjectionTestExecutionListener.class})
public class AServiceMockTest extends BaseTest {
    @Spy
    private BServiceImpl bserviceImpl;
    @Autowired
    private AService aservice;
    @Before
    public void setup(){
        doReturn(true).when(bserviceImpl).b2(any(String.class));
    }
    @Test
    public void test() {
        a.execute();
    }
}
@Service
public class AServiceImpl implements AService {
    @Autowired
    private BService bservice;
     
    @Override
    public boolean execute2() {
        String str = bservice.b1();
        return bservice.b2(str);
    }
}
MockitoDependencyInjectionTestExecutionListener的实现
public class MockitoDependencyInjectionTestExecutionListener extends DependencyInjectionTestExecutionListener {

    private Set injectFields = new HashSet<>();
    private Map mockObjectMap = new HashMap<>();
    @Override
    protected void injectDependencies(TestContext testContext) throws Exception {
        super.injectDependencies(testContext);
        init(testContext);
    }

    /**
     * when A dependences on B
     * mock B or Spy on targetObject of bean get from Spring IoC Container whose type is B.class or beanName is BImpl
     * @param testContext
     */
    private void init(TestContext testContext) throws Exception {

        AutowireCapableBeanFactory factory =testContext.getApplicationContext().getAutowireCapableBeanFactory();
        Object bean = testContext.getTestInstance();
        Field[] fields = bean.getClass().getDeclaredFields();

        for (Field field : fields) {
            Annotation[] annotations = field.getAnnotations();
            for (Annotation annotation : annotations) {
                if(annotation instanceof Mock){
                    Class clazz = field.getType();
                    Object object = Mockito.mock(clazz);
                    field.setAccessible(true);
                    field.set(bean, object);
                    mockObjectMap.put(field.getName(), object);
                } else if(annotation instanceof Spy) {
                    Object fb = factory.getBean(field.getName()); //may be a proxy that can not be spy because $Proxy is final
                    Object targetSource = AopTargetUtils.getTarget(fb);
                    Object spyObject = Mockito.spy(targetSource);
                    if (!fb.equals(targetSource)) { //proxy
                        if (AopUtils.isJdkDynamicProxy(fb)) {
                            setJdkDynamicProxyTargetObject(fb, spyObject);
                        } else { //cglib
                            setCglibProxyTargetObject(fb, spyObject);
                        }
                    } else {
                        mockObjectMap.put(field.getName(), spyObject);
                    }
                    field.setAccessible(true);
                    field.set(bean, spyObject);
                }else if (annotation instanceof Autowired){
                    injectFields.add(field);
                }
            }
        }
        for(Field field: injectFields) {
            field.setAccessible(true);
            Object fo = field.get(bean);
            if (AopUtils.isAopProxy(fo)) {
                Class targetClass = AopUtils.getTargetClass(fo);
                if(targetClass ==null)
                    return;
                Object targetSource = AopTargetUtils.getTarget(fo);
                Field[] targetFields =targetClass.getDeclaredFields();
                for(Field targetField : targetFields){
                    targetField.setAccessible(true);
                    if(mockObjectMap.get(targetField.getName()) ==null){
                        continue;
                    }
                    ReflectionTestUtils.setField(targetSource,targetField.getName(), mockObjectMap.get(targetField.getName()));
                }

            } else {
                Object realObject = factory.getBean(field.getType());
                if(null != realObject) {
                    Field[] targetFields = realObject.getClass().getDeclaredFields();
                    for(Field targetField : targetFields){
                        targetField.setAccessible(true);
                        if(mockObjectMap.get(targetField.getName()) ==null){
                            continue;
                        }
                        ReflectionTestUtils.setField(fo,targetField.getName(), mockObjectMap.get(targetField.getName()));
                    }
                }
            }
        }
    }

    private void setCglibProxyTargetObject(Object proxy, Object spyObject) throws NoSuchFieldException, IllegalAccessException {
        Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
        h.setAccessible(true);
        Object dynamicAdvisedInterceptor = h.get(proxy);
        Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
        advised.setAccessible(true);
        ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).setTarget(spyObject);

    }

    private void setJdkDynamicProxyTargetObject(Object proxy, Object spyObject) throws NoSuchFieldException, IllegalAccessException {
        Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
        h.setAccessible(true);
        AopProxy aopProxy = (AopProxy) h.get(proxy);
        Field advised = aopProxy.getClass().getDeclaredField("advised");
        advised.setAccessible(true);
        ((AdvisedSupport) advised.get(aopProxy)).setTarget(spyObject);
    }
}
maven依赖

JUnit、Mockito

AopTargetUtils

AopTargetUtils工具类参考 在spring中获取代理对象代理的目标对象工具类

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76729.html

相关文章

  • Spring、Spring Boot和TestNG测试指南 - 使用Mockito

    摘要:例子使用源代码我们先给了一个的实现然后又规定了方法的返回值。源代码也就是说,得益于,我们能够很方便地对依赖关系中任意层级的任意做。 Github地址 Mock测试技术能够避免你为了测试一个方法,却需要自行构建整个依赖关系的工作,并且能够让你专注于当前被测试对象的逻辑,而不是其依赖的其他对象的逻辑。 举例来说,比如你需要测试Foo.methodA,而这个方法依赖了Bar.methodB,...

    Alliot 评论0 收藏0
  • 基于 Redis 分布式锁

    摘要:首先谈到分布式锁自然也就联想到分布式应用。如基于的唯一索引。基于的临时有序节点。这里主要基于进行讨论。该命令可以保证的原子性。所以最好的方式是在每次解锁时都需要判断锁是否是自己的。总结至此一个基于的分布式锁完成,但是依然有些问题。 showImg(https://segmentfault.com/img/remote/1460000014128437?w=2048&h=1365); 前...

    fasss 评论0 收藏0
  • Spring Boot 单元测试二三事

    摘要:但是,一个好的单元测试应该是毫秒级的,否则这会影响的工作方式,这也就是测试驱动开发的思想。在单元测试中,我们可以像这样来构建一个实例。所以,我们在写单元测试的时候,应该以一种更简单的方式去构建。 本文翻译自:https://reflectoring.io/unit-...原文作者:Tom Hombergs 译文原地址:https://weyunx.com/2019/02/04... ...

    xbynet 评论0 收藏0
  • Spring Boot 2.1.2 & Spring Cloud Greenwich 升级

    摘要:节前没有新业务代码,正好刚发布,于是开始为期四天的框架代码升级。还好并没有使用它的,配置上有一个小坑,的是表示而是表示,之前配置成的,如果到的里面那就要抛异常了。 节前没有新业务代码,正好Greenwich刚发布,于是开始为期四天的框架代码升级。 之前的版本是 spring boot 1.5.10 , spring cloud Edgware.SR3 依赖升级 增加依赖管理插件 ap...

    newsning 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<