提升单元测试体验的利器--Mockito使用总结

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 为神马要使用Mockito?  在编写单元测试的时候,为了尽可能的保证隔离性,我们时常需要对某些不容易构造或者不容易获取或者对外部环境有依赖的对象,用一个虚拟的对象来创建以便于测试.假设你正在开发的的代码中使用到了公司其他部门的接口(通过RPC服务),当编写单元测试的时候你可能为了不让接口真的去调...

为神马要使用Mockito?

  在编写单元测试的时候,为了尽可能的保证隔离性,我们时常需要对某些不容易构造或者不容易获取或者对外部环境有依赖的对象,用一个虚拟的对象来创建以便于测试.假设你正在开发的的代码中使用到了公司其他部门的接口(通过RPC服务),当编写单元测试的时候你可能为了不让接口真的去调用rpc服务而mock一个接口的对象,最原始的方式是自己手工编写一个该接口的实现类,并且在单元测试的时候注入这个对象,而使用Mockito则可以让我们方便地创建和配置mock对象,使用mockito可以简化对外部环境的依赖.

创建mock对象

  这里我们以一个操作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对象会记住这些交互,在单元测试的执行中碰到代码中的对应方法会默认执行被你自定义的方法内容.

  还是以RedisUtil为例,对方法设定返回值:

when(redisUtil.getValueByKey("key1")).thenReturn("value1");
when(redisUtil.getValueByKey("key2")).thenReturn("value2");

  对方法设定返回自定义的异常信息:

when(redisUtil.getValueByKey("key1")).thenThrow(new RuntimeException);

  此外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为例,

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.

参考文档

  Mockito文档

   

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
30天前
|
IDE Java 测试技术
单元测试问题之Mockito 3.4mock静态方法如何解决
单元测试问题之Mockito 3.4mock静态方法如何解决
24 1
|
30天前
|
Java 测试技术 Maven
单元测试问题之在Maven项目中引入JUnit 5和Mockito的依赖如何解决
单元测试问题之在Maven项目中引入JUnit 5和Mockito的依赖如何解决
91 1
|
2月前
|
测试技术
详解单元测试问题之Mockito中添加或更新打桩列表如何解决
详解单元测试问题之Mockito中添加或更新打桩列表如何解决
40 3
|
2月前
|
测试技术
详解单元测试问题之Mockito中@Mock注解的执行步骤如何解决
详解单元测试问题之Mockito中@Mock注解的执行步骤如何解决
24 2
|
2月前
|
Java 测试技术 API
详解单元测试问题之Mockito的注入过程如何解决
详解单元测试问题之Mockito的注入过程如何解决
49 1
|
1月前
|
测试技术 开发者
单元测试问题之在Mockito中静态方法的调用,如何模拟
单元测试问题之在Mockito中静态方法的调用,如何模拟
|
3月前
|
Java 测试技术 持续交付
Springboot中JUNIT5单元测试+Mockito详解
Springboot中JUNIT5单元测试+Mockito详解
370 1
|
3月前
|
Java 测试技术 数据库
Java一分钟之-Mockito:模拟对象测试
【6月更文挑战第4天】Mockito是Java单元测试中的模拟框架,用于创建和配置模拟对象以隔离测试代码。核心概念包括:模拟对象、预期行为(定义方法调用响应)、验证(检查方法调用)和捕获参数。常见问题包括过度模拟、忽略未使用的模拟调用、不恰当配置和误用Mockito注解。解决方案包括正确选择模拟对象、验证所有交互、仔细配置模拟行为及在测试类中正确使用Mockito注解。提供的代码示例展示了如何使用Mockito模拟和验证方法调用,以实现独立且准确的测试。学习和避免这些易错点可提升测试效率和代码质量。
84 0
Java一分钟之-Mockito:模拟对象测试
|
4月前
|
XML Java 测试技术
【Java技术专题】「核心技术提升」最流行的Java模拟框架Mockito入门指南(Java单元测试)
【Java技术专题】「核心技术提升」最流行的Java模拟框架Mockito入门指南(Java单元测试)
70 0
|
4月前
|
Java 测试技术 API
【Java技术深入解析】「核心技术提升」最流行的Java模拟框架Mockito入门指南(Java单元测试)
还在使用 Mockito 1.x?看看 Mockito 2 有哪些新功能!Mockito 3 没有引入任何破坏性的 API 变动,但现在需要 Java 8 而不是 Mockito 2 的 Java 6。 Mockito 4 删除了过时的 API。Mockito 5 将默认 mockmaker 改为 mockito-inline,现在需要 Java 11。一次只支持一个主要版本,而且不会向旧版本回传更改内容。
126 0
【Java技术深入解析】「核心技术提升」最流行的Java模拟框架Mockito入门指南(Java单元测试)