在编写单元测试的时候,为了尽量的保证隔离性,咱们时常须要对某些不容易构造或者不容易获取或者对外部环境有依赖的对象,用一个虚拟的对象来建立以便于测试.假设你正在开发的的代码中使用到了公司其余部门的接口(经过RPC服务),当编写单元测试的时候你可能为了避免让接口真的去调用rpc服务而mock一个接口的对象,最原始的方式是本身手工编写一个该接口的实现类,而且在单元测试的时候注入这个对象,而使用Mockito则可让咱们方便地建立和配置mock对象,使用mockito能够简化对外部环境的依赖.redis
这里咱们以一个操做Redis的工具类来举例,下面是代码:函数
public class RedisUtil { @Autowired private RedisOperations<String, String> redisTemplate; public boolean checkKeyExists(String key) { return redisTemplate.hasKey(key); } public void setValueByKey(String key, String value) { redisTemplate.opsForValue().set(key, value); }
public String getValueByKey(String key) { return redisTemplate.opsForValue().get(key); } public List<String> getMutiValuesByList(List<String> keys){ return redisTemplate.opsForValue().multiGet(keys); } public void deleteKey(String key) { redisTemplate.delete(key); } public void increValue(String key,Long count){ redisTemplate.opsForValue().increment(key,count); } }
有两种方式能够方便的建立mock对象,第一种方式是工具
Mockito.mock(RedisUtil.class);
还有一种方式在注入时使用@mock注解:单元测试
@mock private RedisUtil redisUtil;
Tips:若是在代码中频繁的使用Mockito比较烦,能够静态导入package,如下例子所有默认已静态导入Mockito包:测试
import static org.mockito.Mockito.*;
当对象被建立以后,就能够对代码中出现的方法进行自定义的交互,mock对象会记住这些交互,在单元测试的执行中碰到代码中的对应方法会默认执行被你自定义的方法内容.spa
仍是以RedisUtil为例,对方法设定返回值:.net
when(redisUtil.getValueByKey("key1")).thenReturn("value1");
when(redisUtil.getValueByKey("key2")).thenReturn("value2");
对方法设定返回自定义的异常信息:code
when(redisUtil.getValueByKey("key1")).thenThrow(new RuntimeException);
此外Mockito还支持迭代风格的返回值定义:xml
when(redisUtil.getValueByKey("key1")).thenReturn("value1").thenReturn("Value2");
即当方法第一次调用redis.getValueBykey("key1")时会返回value1,当再次被调用时则会返回value2.这里须要注意的是,当后续再出现调用的时候返回值都会是value2,并且这种迭代风格的定义支持return和Throw的混搭,即你能够控制在函数调用的第一次去抛出一个异常,而在函数调用的第二次绘制一个正常的值.对象
首先,测试中对于返回值为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()等等
以上所讲的对象都是mock对象,mock对象只能调用打桩方法,不能调用真实方法,使用Spy可让咱们可以监视一个真实对象,既能够对这个对象的某一个函数打桩返回咱们指望的值,也能够去调用真实的方法,建立spy对象的方式和mock相似,不一样的一点是spy须要传一个真实对象而不是一个CLass对象.这里以一个List为例,
List spy = spy(new LinkedList()); when(spy.get(0)).thenReturn("value1"); doReturn("value2").when(spy).get(0);
上面第二行代码,调用when(spy.get(0)),会去调用真实的方法,会抛出异常,第三行代码则不会去调用真实方法,而返回value2.因此总结一下就是,当使用when去模拟返回值的时候,真是方法会被调用,而是用doReturn()去设置的话,则不会去执行真实方法.
须要注意在使用时应该尽可能避免使用spy.