为神马要使用Mockito?
在编写单元测试的时候,为了尽可能的保证隔离性,我们时常需要对某些不容易构造或者不容易获取或者对外部环境有依赖的对象,用一个虚拟的对象来创建以便于测试.假设你正在开发的的代码中使用到了公司其他部门的接口(通过RPC服务),当编写单元测试的时候你可能为了不让接口真的去调用rpc服务而mock一个接口的对象,最原始的方式是自己手工编写一个该接口的实现类,并且在单元测试的时候注入这个对象,而使用Mockito则可以让我们方便地创建和配置mock对象,使用mockito可以简化对外部环境的依赖.
创建mock对象
这里我们以一个操作Redis的工具类来举例,下面是代码:
publicclassRedisUtil{
@Autowired
privateRedisOperations
publicbooleancheckKeyExists(Stringkey){
returnredisTemplate.hasKey(key);
}
publicvoidsetValueByKey(Stringkey,Stringvalue){
redisTemplate.opsForValue().set(key,value);
}
publicStringgetValueByKey(Stringkey){
returnredisTemplate.opsForValue().get(key);
}
publicList
returnredisTemplate.opsForValue().multiGet(keys);
}
publicvoiddeleteKey(Stringkey){
redisTemplate.delete(key);
}
publicvoidincreValue(Stringkey,Longcount){
redisTemplate.opsForValue().increment(key,count);
}
}
有两种方式可以方便的创建mock对象,第一种方式是
Mockito.mock(RedisUtil.class);
还有一种方式在注入时使用@mock注解:
@mock
privateRedisUtilredisUtil;
Tips:如果在代码中频繁的使用Mockito比较烦,可以静态导入package,以下例子全部默认已静态导入Mockito包:
importstaticorg.mockito.Mockito.*;
为测试函数打桩的常用方法
当对象被创建之后,就可以对代码中出现的方法进行自定义的交互,mock对象会记住这些交互,在单元测试的执行中碰到代码中的对应方法会默认执行被你自定义的方法内容.
还是以RedisUtil为例,对方法设定返回值:
when(redisUtil.getValueByKey("key1")).thenReturn("value1");
when(redisUtil.getValueByKey("key2")).thenReturn("value2");
对方法设定返回自定义的异常信息:
when(redisUtil.getValueByKey("key1")).thenThrow(newRuntimeException);
此外Mockito还支持迭代风格的返回值定义:
when(redisUtil.getValueByKey("key1")).thenReturn("value1").thenReturn("Value2");
即当方法第一次调用redis.getValueBykey("key1")时会返回value1,当再次被调用时则会返回value2.这里需要注意的是,当后续再出现调用的时候返回值都会是value2,而且这种迭代风格的定义支持return和Throw的混搭,即你可以控制在函数调用的第一次去抛出一个异常,而在函数调用的第二次绘制一个正常的值.
Mockito如何mock返回值为void的方法
首先,测试中对于返回值为void的方法进行mock本身是没有什么效果的,Mockito有一个doNothing方法是void方法的默认返回:
doNothing().when(redisUtil).increValue(“key1",1L);
其实这里使用doNothing来mock这个方法并没有什么意义,因为我们mock一个方法的目的无非有两个,第一,在某一中输入环境中模拟返回我们期待的返回值,第二就是当方法抛出异常时能够在我们预期控制之下而不会导致单元测试失败,因此对于返回值为void的方法,我们一般可以不去mock它或者使用doThrow()来为void函数打一个桩,当出现异常的时候mock他的异常返回,当不会有异常发生时,只需要在调用后,verify()一下,验证方法的被调用次数即可.
verify(redisUtil,times(1)).increValue("key1",1L);
代码中的times(1)表示一次,即代码中increValue()返回被调用一次的时候能够通过,还可以支持更加广泛的定义,
never():表示从未被调用
atleastOnce():表示至少被调用一次
atleast(3):表示至少被调用3次
atMost(7):表示最多被调用7次
参数匹配器
这里主要介绍一下内置的几个参数匹配器,其实也很好理解,还是那上面的redisUtil为例,对于redisUtil.getValueByKey来说,我希望对于任意的key都返回同一个值,那就可以这么写:
when(redisUtil.getValueByKey(anyString()).thenReturn("value1")
这样在单元测试过程中,对于任意的输入参数,该方法都会返回value1,相同的类型还有很多anyLong(),anyInt(),anyList()等等
使用Spy对象来监控真实对象
以上所讲的对象都是mock对象,mock对象只能调用打桩方法,不能调用真实方法,使用Spy可以让我们能够监视一个真实对象,既可以对这个对象的某一个函数打桩返回我们期望的值,也可以去调用真实的方法,创建spy对象的方式和mock类似,不同的一点是spy需要传一个真实对象而不是一个CLass对象.这里以一个List为例,
Listspy=spy(newLinkedList());
when(spy.get(0)).thenReturn("value1");
doReturn("value2").when(spy).get(0);
上面第二行代码,调用when(spy.get(0)),会去调用真实的方法,会抛出异常,第三行代码则不会去调用真实方法,而返回value2.所以总结一下就是,当使用when去模拟返回值的时候,真是方法会被调用,而是用doReturn()去设置的话,则不会去执行真实方法.
需要注意在使用时应该尽量避免使用spy.
提升单元测试体验的利器--Mockito使用总结
发表于:2017-08-06
作者:醉眼识朦胧
来源:
 相关文章
单元测试系列之一开篇 单元测试的实践与思考 如何用 JavaScript 编写你的第一个单元测试 如何写出有效的单元测试 Java单元测试用例的编写,有什么技巧? 单元测试:优雅的Spock框架- 周排行
- 月排行
-   白盒测试怎么测?
-   单元测试指南
-   淘系用户平台技术团队单元测试建设
-   单元测试中捕获异步方法的指定异常
-   C#中单元测试如何部署配置文件?
-   软件测试之单元测试
-   使用Microsoft Fakes进行单元测试(2)
-   一次单元测试优化的过程总结
-   白盒测试怎么测?
-   Android 单元测试,从小白到入门开始
-   鹅厂开发谈单元测试:牢记这两点,从...
-   测试驱动开发实践:如何使用 Xunit ...
-   什么是单元测试,为什么要做单元测试
-   单元测试指南