前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Boot、Dubbo项目Mock测试踩坑与总结

Spring Boot、Dubbo项目Mock测试踩坑与总结

作者头像
用户1516716
发布2018-04-02 16:41:20
3.7K0
发布2018-04-02 16:41:20
举报
文章被收录于专栏:A周立SpringCloudA周立SpringCloud

本文是对Spring Boot、Dubbo项目进行Mock测试的总结与踩坑实录。

搜索了一圈,居然没发现类似的文章,莫非用Dubbo的朋友们都不Mock测试,或者有其他的办法测试吗?

简单总结了一下,希望对大家能有一定参考意义。如果有更好的测试方法,请联系我的邮箱eacdy0000@126.com ,帮忙告知一下,不胜感激。另,本文代码较多,微信体验可能不佳,可前往http://www.itmuch.com/dubbo/spring-boot-dubbo-mock/ 详细阅读(点击原文即可)

一、背景

手上有个整合了Dubbo的Spring Boot应用,在应用中需要消费其他服务的API。由于我依赖的服务并不由我所在的项目组维护(对方可能接口中途会发生变化,甚至,有时候可能并未启动)。

集成测试成本略高,故而想办法Mock测试。以RemoteApi为例,这是一个远程的API。我这一侧(消费者)的代码如下:

@Service public class MyApi { @Reference private RemoteApi remoteApi; public String hold() { return remoteApi.hold(); } }

由代码可知,MyApi调用了一个远程的API RemoteApi。

下面我们来mock测试。

二、整合powermock

经过调研,笔者选择powermock作为项目的mock工具。

(1) 加依赖:

<dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>1.6.6</version> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito</artifactId> <version>1.6.6</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4-rule</artifactId> <version>1.6.6</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-classloading-xstream</artifactId> <version>1.6.6</version> <scope>test</scope> </dependency>

(2) 测试启动类:

@SpringBootApplication public class ConsumerTest { public static void main(String[] args) { SpringApplication.run(ConsumerTest.class, args); } @Bean public RemoteApi RemoteApi() { RemoteApi remoteApi = PowerMockito.mock(RemoteApi.class); PowerMockito.when(remoteApi.hold()) .thenAnswer(t -> "我是Mock的API。"); return remoteApi; } }

由代码可知,我在这里mock了一个RemoteApi,当调用Mock的RemoteApi.hold()方法时,返回

我是Mock的API。

(3) 测试类:

@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = ConsumerTest.class) public class MyApiTest { @Autowired public MyApi myApi; @Test public void hold() { Assert.assertEquals("我是Mock的API。", this.myApi.hold()); } }

(4) 执行单元测试,发现Mock并没有成功,Dubbo依然会尝试调用远程API,而并非笔者Mock的RemoteApi。

三、分析

Mock没有成功,为什么呢?我们不妨将测试类代码修改成如下:

@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = ConsumerTest.class) public class MyApiTest { @Autowired private ApplicationContext applicationContext; @Before public void before() { MyApi myApi = applicationContext.getBean(MyApi.class); RemoteApi fromMyApi = myApi.getRemoteApi(); RemoteApi fromSpring = applicationContext.getBean(RemoteApi.class); System.out.println("MyApi中注入的RemoteApi是:" + fromMyApi); System.out.println("Spring容器中注入的RemoteApi是:" + fromSpring); } @Autowired public MyApi myApi; @Test public void hold() { Assert.assertEquals("我是Mock的API。", this.myApi.hold()); } }

从代码不难发现,我在执行单元测试之前,分别打印MyApi对象中注入的RemoteApi,以及Spring容器中的RemoteApi(@Bean所注解的方法,new出来的对象,必然在Spring容器中)。

执行后,打印结果如下:

MyApi中注入的RemoteApi是:com.alibaba.dubbo.common.bytecode.proxy0@541afb85 Spring容器中注入的RemoteApi是:remoteApi

由打印结果可知,MyApi中注入的RemoteApi和容器中的RemoteApi,压根不是一个实例。

由该结果,可以知道2点:

(1) Dubbo的@Reference注解拿到的一个代理;

(2) @Reference生成的代理并不在Spring容器中(如果Dubbo的Reference的代理也是容器中,那么容器中应该有2个RemoteApi实例,那么调用getBean()应当报错);

四、解决

原因我们知道了,要如何解决呢?答案很简单——如果我们在执行单元测试之前,将StoreApi中注入的RemoteApi换成Spring容器中的实例(即我们Mock的那个对象),那么问题就可以得到就解决。

@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = ConsumerTest.class) public class MyApiTest { @Autowired private ApplicationContext applicationContext; @Before public void before() { MyApi myApi = applicationContext.getBean(MyApi.class); RemoteApi fromSpring = applicationContext.getBean(RemoteApi.class); myApi.setRemoteApi(fromSpring); } @Autowired public MyApi myApi; @Test public void hold() { Assert.assertEquals("我是Mock的API。", this.myApi.hold()); } }

再次执行,就会发现,现在已经可以正常Mock了。

五、源码分析

以上已经提供了解决方案。那么,@Reference注解究竟干了哪些事情呢?我们不妨分析一下。搜索@Reference注解被哪些地方使用,可找到以下代码:com.alibaba.dubbo.config.spring.AnnotationBean#postProcessBeforeInitialization 。以该代码是我们定位问题的入口,由此,我们可以定位到以下两个方法:

  1. com.alibaba.dubbo.config.ReferenceConfig#init
  2. com.alibaba.dubbo.config.ReferenceConfig#createProxy

其中,

  1. createProxy方法用于创建代理对象;
  2. init方法用来判断是否已经初始化,如果没有初始化,就会调用createProxy创建代理对象。过程比较简单,不贴了。

了解Dubbo如何创建对象后,我们来看看Dubbo是如何将代理对象设置到MyApi的,如下图。

分析至此,大家应该能够了解原因了——

  1. @Reference创建了一个代理;
  2. Dubbo自身做了一些判断,如果发现没有初始化,就会创建一个代理;
  3. 在postProcessBeforeInitialization 方法中,从Spring容器中拿到MyApi对象,并将这个代理对象设到MyApi实例中。

六、Going Far

我们已经知道,是@Reference注解搞的鬼,除了以上解决方案,还可以弄一个类,转一下。

即:原调用链:

MyApi —> RemoteApi

改为:

MyApi —> 一个转换的类,啥都不干,用了@Service注解,在里面调用RemoteApi的方法 —> RemoteApi

七、WHATS MORE OVER

如果使用xml配置,不存在该问题,可以很简单地Mock。

八、配套代码

https://github.com/itmuch/spring-boot-dubbo-mock-sample

九、版权说明

本文采用 CC BY 3.0 CN协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-05-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 A周立SpringCloud 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档