PowerMock单元测试踩坑与总结
1.Mock是什么?
通过提供定制的类加载器以及一些字节码篡改技巧的应用,PowerMock 现了对静态方法、构造方法、私有方法以及 Final 方法的模拟支持,对静态初始化过程的移除等强大的功能。
2.为什么要用PowerMock?
举个例子:当测试单机应用的时候,直接写Junit单元测试即可,但当涉及到多个服务时,你写好了你的服务,其它服务尚未完成,这时候就需要模拟调用远程服务,也就需要Mock。
3.Mock的流程
简单来说,模拟测试一共分为4步:数据准备、打桩(Mock)、执行、验证。
数据准备阶段可以为Mock阶段的准备期望值、参数等数据,执行mock对象的方法后,最后进行验证与判断。
@RunWith(PowerMockRunner.class)
@PrepareForTest(PrivatePartialMockingExample.class)
public class PowerMockTest {
@Test
public void demoPrivateMethodMocking() throws Exception {
//数据准备
final String expected = "TEST VALUE";
PrivatePartialMockingExample underTest = PowerMockito.spy(new PrivatePartialMockingExample());
// mock || 打桩
// PowerMockito.when(underTest, nameOfMethodToMock, input).thenReturn(expected);
PowerMockito.doReturn(expected).when(underTest).methodToTest();
// 执行
String toTest = underTest.methodToTest();
// 验证
// doReturn 设置不会执行when()后的method()方法,依旧会获得和期望值一样的结果
assertEquals(expected, toTest);
}
}
public class PrivatePartialMockingExample {
public String methodToTest() {
System.out.println("public Method");
return methodToMock("input");
}
private String methodToMock(String input) {
System.out.println("private Method");
return "REAL VALUE = " + input;
}
}
4.Mock的细节
@PrepareForTest(IdGenerator.class)
告诉PowerMock为测试准备某些类,放在测试类和单独的测试方法。支持通配符 @PrepareForTest("com.mypackage.*")
mock和spy
使用Mock生成的类,所有方法都不是真实的方法,而且返回值都是NULL。
使用Spy生成的类,所有方法都是真实方法,返回值都是和真实方法一样的。
when和doReturn
when(dao.getOrder()).thenReturn("returened by mock ");
doReturn(expected).when(underTest).methodToTest();
使用when去设置模拟返回值时,它里面的方法(dao.getOrder())会先执行一次。
使用doReturn去设置的话,就不会产生上面的问题,因为有when来进行控制要模拟的方法,所以不会执行原来的方法。
doNothing()和verify()
doNothing() 用于模拟void方法,其实不做任何事。
Mockito.verify() 验证某些方法使用了几次(默认1次),否定则抛出异常
@Test
public void getTotalEmployee() {
EmployeeService service = PowerMockito.mock(EmployeeService.class);
//doNothing() 用于执行void方法,不做任何事
PowerMockito.doNothing().when(service).getTotalEmployee();
service.getTotalEmployee();
//验证某些方法使用了几次(默认1次),否定则抛出异常
Mockito.verify(service,Mockito.times(1)).getTotalEmployee();
}
whenNew和withArguments
whenNew 模拟new行为,并不会真的创建对象,withArguments 传入构造函数的参数,无参使用 withNoArguments()
@RunWith(PowerMockRunner.class)
@PrepareForTest(DirectoryStructure.class)
public class DirectoryStructureTest {
@Test
public void createDirectoryStructureWhenPathDoesntExist() throws Exception {
final String directoryPath = "mocked path";
File directoryMock = PowerMockito.mock(File.class);
// 这就是如何告诉PowerMockito模拟新文件的构造。
PowerMockito.whenNew(File.class).withArguments(directoryPath).thenReturn(directoryMock);
//验证某个行为发生过几次(默认1次)
PowerMockito.verifyNew(File.class,times(0)).withArguments(directoryPath);
}
}
Arguments Matcher
一个作为参数的接口类,可以应对“根据不同参数返回不同值”的场景。
PowerMockito.when(demoService.findNameById(Mockito.argThat(new ArgumentMatcher<String>() {
@Override
public boolean matches(String s) {
if (s.equals("Jerry")) {
return true;
} else {
return false;
}
}
}))).thenReturn("Marry");
System.out.println(demoService.findNameById("Jerry")); //返回Marry
System.out.println(demoService.findNameById("NotJerry")); //返回null
Answer interface
一个作为参数的接口类,可以应对“根据不同参数返回不同值”的场景。更强大。
PowerMockito.when(demoService.findNameById(Mockito.anyString())).then(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) {
Object[] arguments = invocationOnMock.getArguments();
String arg = (String) arguments[0];
if (arg.equals("Jerry")){
return "Marry";
}else {
return "Not";
}
}
});
System.out.println(demoService.findNameById("Jerry")); //返回Marry
System.out.println(demoService.findNameById("NotJerry")); //返回Not
任意数
ArgumentMatchers.anyLong()
示例:
PowerMockito.doReturn(application).when(Application).getById(ArgumentMatchers.anyLong());
注意:不可以出现
PowerMockito.doReturn(application).when(Application).getById(ArgumentMatchers.anyLong(), object);
上面的这种形式,要么全用虚拟参数,要么不用。而且,ArgumentMatchers只适用于此处,不能在其它地方使用。
5.MockMVC、PowerMock集成Spring
首先看我的依赖,对于mockito-core的2.8.55版本我一直无法进行Maven下载,无奈手动下载导包。
<properties>
<java.version>1.8</java.version>
<powermock.version>1.7.1</powermock.version>
<mock.version>2.8.55</mock.version>
</properties>
<dependencies>
<!-- mockito-core 手动安装 -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock.version}</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-testng</artifactId>
<version>${powermock.version}</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-classloading-xstream</artifactId>
<version>${powermock.version}</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4-rule</artifactId>
<version>${powermock.version}</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-support</artifactId>
<version>${powermock.version}</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
假如我的Controller层有这么一个方法
@RestController
public class DemoController {
@Autowired
DemoService demoService;
@RequestMapping("/demo")
public User mapping(){
return demoService.create();
}
}
他的DemoService方法是下面这个简单的例子
@Service
public class DemoService {
public User create(){
return new User("service",1);
}
}
我只想测试这个接口,那么我可以这么写测试用例
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MockdemoApplication.class)
public class MockdemoApplicationTests{
@Autowired
private MockMvc mockMvc;
@Test
public void contextLoads() throws Exception {
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/demo")).andReturn();
String content = result.getResponse().getContentAsString();
System.out.println(content);
}
}
关于MockMvc需要我们手动注入,我写了一个Bean专门做注入,这个Bean实现了ApplicationListener接口,原因后面会讲到。
@Configuration
public class AutoPowerMock implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private WebApplicationContext webApplicationContext;
@Bean
public MockMvc getMockMvc() {
return MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
}
这样执行我的测试用例,是可以完全成功的!
但需要考虑的是,如果在应用中需要消费其他服务的API。由于我依赖的服务并不由我所在的项目组维护(对方可能接口中途会发生变化,甚至,有时候可能并未启动)。集成测试成本略高,故而需要Mock测试。
对于任意一个类的Mock,开始我们已经讲过,那对于Spring的Bean的Mock,就需要多费点心思。
考虑到@Autowired注解会把Bean注入到测试类中,而且在程序运行时,调用的也都是Spring容器的Bean,所以一个简单的思路就是:把Spring容器中的Bean替换成我们Mock后的Bean。
这就用到上面的对ApplicationListener的实现类了,它的onApplicationEvent方法会在Bean加载完后调用,在调用时我们手动对Bean进行偷天换日,完整代码如下
@Configuration
public class AutoPowerMock implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private WebApplicationContext webApplicationContext;
@Bean
public MockMvc getMockMvc() {
return MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
//这是需要Mock掉的Bean
Class<?> value = DemoService.class;
//获取BeanFactory
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) webApplicationContext.getAutowireCapableBeanFactory();
//Mock
Object mock = PowerMockito.mock(value);
//替换
String[] beanNames = beanFactory.getBeanNamesForType(value);
for (String bean : beanNames) {
beanFactory.removeBeanDefinition(bean);
beanFactory.registerSingleton(bean, mock);
}
}
}
我收藏的学习资源
Spring Boot、Dubbo项目Mock测试踩坑与总结
本文已授权公众号后端技术精选发布
PowerMock单元测试踩坑与总结的更多相关文章
- vue2项目,踩坑Jest单元测试
目前的项目已经维护了挺久,由于客户要求,我们要为项目加上单元测试,挑选一番后选择了Jest(配置简便,开箱即用),下面记录了此次为项目添加Jest作为单元测试的经历. 安装Jest 1. 在项目目录下 ...
- 【踩坑速记】二次依赖?android studio编译运行各种踩坑解决方案,杜绝弯路,总有你想要的~
这篇博客,只是把自己在开发中经常遇到的打包编译问题以及解决方案给大家稍微分享一下,不求吸睛,但求有用. 1.大家都知道我们常常会遇到dex超出方法数的问题,所以很多人都会采用android.suppo ...
- HashMap踩坑实录——谁动了我的奶酪
说到HashMap,hashCode 和 equals ,想必绝大多数人都不会陌生,然而你真的了解这它们的机制么?本文将通过一个简单的Demo还原我自己前不久在 HashMap 上导致的线上问题,看看 ...
- Spring5.x源码分析 | 从踩坑到放弃之环境搭建
Spring5.x源码分析--从踩坑到放弃之环境搭建 前言 自从Spring发行4.x后,很久没去好好看过Spring源码了,加上最近半年工作都是偏管理和参与设计为主,在技术细节上或多或少有点疏忽,最 ...
- Spark踩坑记——Spark Streaming+Kafka
[TOC] 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark strea ...
- Spark踩坑记——数据库(Hbase+Mysql)
[TOC] 前言 在使用Spark Streaming的过程中对于计算产生结果的进行持久化时,我们往往需要操作数据库,去统计或者改变一些值.最近一个实时消费者处理任务,在使用spark streami ...
- NPOI导出Excel (C#) 踩坑 之--The maximum column width for an individual cell is 255 charaters
/******************************************************************* * 版权所有: * 类 名 称:ExcelHelper * 作 ...
- 我的微信小程序入门踩坑之旅
前言 更好的阅读体验请:我的微信小程序入门踩坑之旅 小程序出来也有一段日子了,刚出来时也留意了一下.不过赶上生病,加上公司里也有别的事,主要是自己犯懒,就一直没做.这星期一,赶紧趁着这股热乎劲,也不是 ...
- router路由去掉#!的踩坑记
项目中在研究去掉router#!的过程中的踩坑过程.
随机推荐
- 性能优化4--Bitmap内存优化
1.Bitmap在Android虚拟机中的内存分配 在Android3.0之前,Bitmap的内存分配分为两部分,一部分是分配在Dalvik的VM堆中.而像素数据的内存是分配在Native堆中,而到了 ...
- LeetCode题解之Clone Graph
1.题目描述 2.问题分析 要遍历图,然后标记没有被复制的节点. 3.代码 class Solution { private: unordered_map<Node*, Node*> m; ...
- [20190312]关于增量检查点的疑问(补充).txt
[20190312]关于增量检查点的疑问(补充).txt --//有人问我以前写一个帖子的问题,关于增量检查点的问题,链接如下:http://blog.itpub.net/267265/viewspa ...
- 利用dockerfile制作基于centos7的lnmp镜像(亲测,详细版)
首先呢,这篇文章,也是小弟参考了许多文章,自己整理出来的,有很多不足之处还有待加强,期待各位评论. > LNMP 是代表 Linux 系统下的 Nginx.Mariadb.PHP 相结合而构建成 ...
- AngularJS学习之旅—AngularJS 指令(三)
1.AngularJS 指令 AngularJS 通过被称为 指令 的新属性来扩展 HTML. AngularJS 通过内置的指令来为应用添加功能. AngularJS 允许你自定义指令.2.Angu ...
- Windows 命令行
1.d: 进入d盘 2.dir(directory) 显示当前文件夹下的所有内容 3.md (make directory) +文件夹名 在当前文件夹下创建文件夹 4.rd (remove ...
- LeetCode算法题-Word Pattern(Java实现)
这是悦乐书的第202次更新,第212篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第68题(顺位题号是290).给定一个模式和一个字符串str,找到str是否完全匹配该模 ...
- Java入门(一):Hello World !
前言 从今天开始,准备写Java Web开发的系列文章,毕竟自己主攻的还是Java方向,Python只是业余学习的兴趣使然,在第二技能还没有培养成熟前,做好第一技能的巩固和提高是很有必要的.从正式入行 ...
- Spring Web项目spring配置文件随服务器启动时自动加载
前言:其实配置文件不随服务器启动时加载也是可以的,但是这样操作的话,每次获取相应对象,就会去读取一次配置文件,从而降低程序的效率,而Spring中已经为我们提供了监听器,可监听服务器是否启动,然后在启 ...
- sqlserver 2000数据压缩解决方法
--sqlserver 2000数据压缩解决方法. /************************************************************************* ...