Java-测试-Mockito 入门篇
之前很长一段时间我都认为测试就是使用SpringBootTest类似下面的写法:
@SpringBootTest
class SysAuthServiceTest {@AutowiredSysRoleAuthMapper sysRoleAuthMapper;@Testpublic void test() {QueryWrapper<SysRoleAuth> queryWrapper = new QueryWrapper<>();queryWrapper.eq("role_id", 1);List<SysRoleAuth> sysRoleAuths = sysRoleAuthMapper.selectList(queryWrapper);assertNotNull(sysRoleAuths);assert sysRoleAuths.size() > 0;}
}
这样也确实是单元测试,只不过这个只是其中的一方面。如果你的某个方法并不依赖于Spring容器,也需要启动整个Spirng环境吗?启动这个环境可能是比较耗时间的。本文对Mockito框架做一个初步探索,找找写单测的感觉。
单元测试规范
https://alibaba.github.io/p3c/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95.html
依赖
本文基于 Junit5 + Mockito 3, 高版本的Mockito 需要高版本的Java支持。比如你使用Mockito 5就需要满足Java版本在11以上。
<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>3.7.7</version><scope>test</scope>
</dependency>
HelloWord - 不需要Spring
在很多时候我们的单元测试是不需Spring的,下面我们来看一个例子。
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class SysRoleServiceTest {@MockRedisHelper redisHelper;@BeforeEachvoid setUp() {when(redisHelper.get(anyString())).thenAnswer((Answer<SysRole>) invocation -> {String key = invocation.getArgument(0);if ("1836664638416211969".equals(key)) {return createRole("admin", "管理员");} else {return createRole("user", "普通用户");}});when(redisHelper.get(anyString(), anyLong())).thenAnswer((Answer<SysRole>) invocation -> {// 通过invocation.getArgument() 获取参数String key = invocation.getArgument(0);Long id = invocation.getArgument(1);if ("1836664638416211969".equals(key) && id == 10L) {return createRole("admin", "管理员");} else {return createRole("user", "普通用户");}});}private SysRole createRole(String roleName, String description) {SysRole role = new SysRole();role.setRoleName(roleName);role.setDescription(description);role.setCreateTime(new Date());role.setUpdateTime(new Date());role.setStatus(1);role.setLogicDelete(0);return role;}@Testvoid test() {String key = "1836664638416211969";Object o1 = redisHelper.get(key, 100);Object o2 = redisHelper.get(key);System.out.println(o1);System.out.println(o2);assertNotEquals(o1, o2);}}
在这个例子中我们定义了一个RedisHelper,模拟了两个重载方法。为什么要加@MockitoSettings(strictness = Strictness.LENIENT)呢?
如果不加会出现下面的错误,这是因为比如我们模拟了两个方法,但是其中一个方法我们并没有使用:

这个错误有两个解决方法:1) 你加上前文中的注解 2)你模拟的方法都被使用了,就不会有这个错误
HelloWord - 需要Spring
在一些时候比如我们就是基于Spring环境来写单测,但比如Redis组件还在安装中又或者当前的Redis不可用,我们可以暂时模拟。通过 @MockBean的方式。与上面的区别是:1)我们用的注解不一样 @Mock 和 @MockBean 2)我们需要加SpringBootTest注解
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@SpringBootTest
class SysRoleServiceTest {@MockBeanRedisHelper redisHelper;@BeforeEachvoid setUp() {when(redisHelper.get(anyString())).thenAnswer((Answer<SysRole>) invocation -> {String key = invocation.getArgument(0);if ("1836664638416211969".equals(key)) {return createRole("admin", "管理员");} else {return createRole("user", "普通用户");}});when(redisHelper.get(anyString(), anyLong())).thenAnswer((Answer<SysRole>) invocation -> {// 通过invocation.getArgument() 获取参数String key = invocation.getArgument(0);Long id = invocation.getArgument(1);if ("1836664638416211969".equals(key) && id == 1L) {return createRole("admin", "管理员");} else {return createRole("user", "普通用户");}});}private SysRole createRole(String roleName, String description) {SysRole role = new SysRole();role.setRoleName(roleName);role.setDescription(description);role.setCreateTime(new Date());role.setUpdateTime(new Date());role.setStatus(1);role.setLogicDelete(0);return role;}@Testvoid test() {String key = "1836664638416211969";Object o1 = redisHelper.get(key, 100);Object o2 = redisHelper.get(key);System.out.println(o1);System.out.println(o2);assertNotEquals(o1, o2);}
}
Mockito 的用法
https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
模拟对象
- 模拟行为
List mockedList = mock(List.class);
mockedList.add("one");
verify(mockedList).add("one");
mockedList.stream().forEach(System.out::println);
// 可以指定模拟策略
MockDemo mock = mock(MockDemo.class, Mockito.RETURNS_SMART_NULLS);
这里我模拟一个List对象,特别要注意的是这个对象不存储数据所以后面的流式打印是不会有任何输出的。这个List的对象只在模拟调用方法的这个行为。比如verify(mockedList).add(“one”);就是用来确认这个方法是不是被调用了一次,而前面我们确实调用了一次add方法。那这个有什么用呢?这个可以再不影响数据的前提下测试我们是不是正确的调用了对象的方法,有没有多次调用或者调用次数符不符合预期。
VerificationModeFactory给我们提供了一些验证模型,比如至少多少次atLeast,当然我们是可以拓展自己的规则的。

- 模拟对象的方法并指定行为
我们可以模拟一个对象的方法,并且指定这个方法调用以后的行为,比如返回一个值,抛出一个异常。
LinkedList mockedList = mock(LinkedList.class);
// 返回一个值
when(mockedList.get(0)).thenReturn("first");
// 抛出一个异常
when(mockedList.get(1)).thenThrow(new RuntimeException());
参数匹配
当然我们的参数可以使用通用匹配:

上面的例子中其实有用到,这里再举个例子,当我们试图从LinkedList 中获取SysRole对象的时候,我们对信息进行处理以后返回:
@Test
public void test() {Map mockedList = mock(HashMap.class);when(mockedList.get(any(SysRole.class))).thenAnswer(invocation -> {SysRole role = invocation.getArgument(0);if (role.getRoleName().equals("test")) {role.setStatus(1);role.setLogicDelete(1);role.setDescription("handled");}return role;});SysRole sysRole = new SysRole();sysRole.setRoleName("test");Object o = mockedList.get(sysRole);System.out.println(o);
}
又例如当添加的参数是字符串1时添加成功:
List mockedList = mock(List.class);
when(mockedList.add(argThat(str -> str.equals("1")))).thenReturn(true);
Object o = mockedList.add("2");
System.out.println(o);
又比如满足第一个参数是int, 第二个参数是字符串,第三个参数是third argument返回yes
MockDemo demo = mock(MockDemo.class);
when(demo.doSomething(anyInt(), anyString(), eq("third argument"))).thenReturn("yes");
String s = demo.doSomething(1, "two", "third argument");
System.out.println(s);
模拟异常
List mockList = mock(List.class);
when(mockList.add(anyInt())).thenThrow(new RuntimeException("clear error"));
doThrow(new RuntimeException()).when(mockList).clear();
when 和 doThrow 的区别是什么?
when 用于定义方法调用时的返回值或行为。
doThrow 专门用于定义方法调用时抛出的异常
保证顺序
直接引用官网示例:
// A. Single mock whose methods must be invoked in a particular orderList singleMock = mock(List.class);//using a single mocksingleMock.add("was added first");singleMock.add("was added second");//create an inOrder verifier for a single mockInOrder inOrder = inOrder(singleMock);//following will make sure that add is first called with "was added first", then with "was added second"inOrder.verify(singleMock).add("was added first");inOrder.verify(singleMock).add("was added second");// B. Multiple mocks that must be used in a particular orderList firstMock = mock(List.class);List secondMock = mock(List.class);//using mocksfirstMock.add("was called first");secondMock.add("was called second");//create inOrder object passing any mocks that need to be verified in orderInOrder inOrder = inOrder(firstMock, secondMock);//following will make sure that firstMock was called before secondMockinOrder.verify(firstMock).add("was called first");inOrder.verify(secondMock).add("was called second");
模拟链
@Mock
List mockList;@Test
public void test() {when(mockList.add("some arg")).thenThrow(new RuntimeException()).thenReturn(true);// 第一次调用抛出异常try {mockList.add("some arg");} catch (RuntimeException e) {}// 第二次调用返回trueSystem.out.println(mockList.add("some arg"));// 连续调用返回trueSystem.out.println(mockList.add("some arg"));
}
在不抛出异常的情况下我们可以进一步简化:
when(mockList.add("some arg")).thenReturn(true, false, true);
System.out.println(mockList.add("some arg"));
// 第二次调用返回true
System.out.println(mockList.add("some arg"));
// 连续调用返回true
System.out.println(mockList.add("some arg"));
结果为:true,false,true
我们需要注意和下面的区别:
when(mockList.add("some arg")).thenReturn(true);
when(mockList.add("some arg")).thenReturn(false);
System.out.println(mockList.add("some arg"));
System.out.println(mockList.add("some arg"));
这里第二个会覆盖第一个,结果为false。
真实模拟
List list = new LinkedList();
List spy = spy(list);
when(spy.size()).thenReturn(100);
spy.add("one");
spy.add("two");
System.out.println(spy.get(0));
System.out.println(spy.get(1));
System.out.println(spy.size());
结果:one,two,100
还记得前文我们提到mock是不会真实存储数据的,只是模拟行为,那么如果要存储真实数据用spy();
这里还有个易错点,你可能会像下面这样写,当get的时候返回一个数据,但是结果会抛出异常,如果你有需要获取还没有插入值的spy对象需要使用doReturn
List list = new LinkedList();
List spy = spy(list);
// 这里直接会抛出IndexOutOfBoundsException
when(spy.get(0)).thenReturn("foo");
// 如果想要解决上面的异常使用doReturn
doReturn("foo").when(spy).get(0);
System.out.println(spy.get(0));
捕获验证参数
public class Person {private String name;public Person(String name) {this.name = name;}public String getName() {return name;}
}
public interface MockDemo {void doSomething(Person person);
}
@Test
public void testDoSomething() {// 创建模拟对象MockDemo mock = mock(MockDemo.class, Mockito.RETURNS_SMART_NULLS);// 创建 Person 对象Person john = new Person("John");// 调用 doSomething 方法mock.doSomething(john);// 使用 ArgumentCaptor 捕获参数ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);// 验证 doSomething 方法被调用,并捕获传入的 Person 参数verify(mock).doSomething(argument.capture());// 验证捕获到的 Person 对象的 name 属性是否为 "John"assertEquals("John", argument.getValue().getName());
}
上面这个例子是在调用doSomething方法的时候,捕获到这个参数,并且判断这个参数是否符合我们的预期。
官网有句话值得注意:
it is recommended to use ArgumentCaptor with verification but not with stubbing. Using ArgumentCaptor with stubbing may decrease test readability because captor is created outside of assert (aka verify or ‘then’) block. Also it may reduce defect localization because if stubbed method was not called then no argument is captured.
调用真实方法
when(mock.someMethod()).thenCallRealMethod();
重置
reset(mock);
之前的行为和打桩都会被重置。
注解
// 是下面代码的简写
// List list = new LinkedList();
// List spy = spy(list);
@Spy BeerDrinker drinker;// 类似于@AutoWired 会进行注入
@InjectMocks LocalPub;
例子:
public class UserService {private final UserRepository userRepository;public UserService(UserRepository userRepository) {this.userRepository = userRepository;}public User getUserById(int id) {return userRepository.findById(id);}
}
public class UserServiceTest {@Mockprivate UserRepository userRepository;@InjectMocksprivate UserService userService;@BeforeEachpublic void setUp() {MockitoAnnotations.openMocks(this);}@Testpublic void testGetUserById() {// 创建模拟数据User user = new User(1, "John Doe");// 设置模拟行为when(userRepository.findById(1)).thenReturn(user);// 调用方法User result = userService.getUserById(1);// 验证结果assertEquals("John Doe", result.getName());}
}
相关文章:
Java-测试-Mockito 入门篇
之前很长一段时间我都认为测试就是使用SpringBootTest类似下面的写法: SpringBootTest class SysAuthServiceTest {AutowiredSysRoleAuthMapper sysRoleAuthMapper;Testpublic void test() {QueryWrapper<SysRoleAuth> queryWrapper new QueryWrapper<&g…...
【jupyter notebook】环境部署及pycharm连接虚拟机和本地两种方式
Python数据处理分析简介 Python作为当下最为流行的编程语言之一 可以独立完成数据分析的各种任务数据分析领域里有海量开源库机器学习/深度学习领域最热门的编程语言在爬虫,Web开发等领域均有应用 与Excel,PowerBI,Tableau等软件比较 Excel有…...
TypeScript异常处理
1.异常的概念 程序运行中意外发生的情况就成为异常 例子: //除法运算function chu(num1:number,num2:number){if(num20){//throw 抛出异常throw new Error(除数不能为零)}let num:numbernum1/num2console.log(num) }//程序出现异常后会停止运行// 捕获异常try{ /…...
go的学习笔记
中文标准库文档:https://studygolang.com/pkgdoc 第一段代码 所有代码的主文件都是main.go,下面的代码直接在项目里面创建main.go运行 package main // 声明文件所在的包,每个go文件必须有归属的包import "fmt" // 引入程序需要的包,为了使用包下的函数,比如Print…...
卷积和转置卷积的输出尺寸计算
卷积和转置卷积的输出尺寸计算 卷积 h是输出的高,h是输入的高,k_h是卷积核的高 w类似stride1 h h - k_h padding*2 1通用公式 stride1就是上面的公式 h (h - k_w 2*padding stride)//stride 一些常见的卷积 高宽不变的卷积:kernel…...
vue3+ts 使用amCharts展示地图,1.点击左侧国家,可以高亮并放大右侧地图对应的国家。 2.展示数据球。
效果图展示: 1.点击左侧国家,可以高亮并放大右侧地图对应的国家。 2.展示数据球。 下载依赖 yarn add amcharts/amcharts5其中,props.countryData的数据格式为 [{ “country”: “加拿大”, “code”: “CA”, “deviceCount”: 1 },{ “c…...
汽车无钥匙启动功能工作原理
移动管家无钥匙启动是一种科技化的汽车启动方式,它允许车主在不使用传统钥匙的情况下启动车辆。这种技术通过智能感应系统实现,车主只需携带智能钥匙,当靠近车辆时,车辆能够自动解锁并准备启动。启动车辆时,车主无…...
C++标准的一些特性记录:C++11的auto和decltype
文章目录 auto容器遍历配合lambda表达式decltype两者对引用类型的处理是相同的decltype保留const,而auto不会保留const在C++11中,引入了两个新的关键字,auto和decltype两个关键字,都是用于做类型推断。但是使用的场景有些区别。 auto 容器遍历 auto这个关键字,我个人在编…...
【Elasticsearch系列四】ELK Stack
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...
【新手上路】衡石分析平台使用手册-认证方式
认证方式 用户登录衡石系统时,系统需要对输入的用户名和密码进行验证,保证系统的安全。衡石提供 CAS、SAML2、OAUTH2等多种单点登录认证方式。在 SSO 单点登录中,衡石是服务提供者 SP(Service Provider)为用户提供所…...
数字电路与逻辑设计-触发器功能测试及其应用
一、实验目的 1.验证基本RS、JK、D、T和T’触发器的逻辑功能及使用方法; 2.能进行触发器之间的相互转换; 3.学习触发器的一些应用。 二、实验原理 触发器具有两个能够自行保持的稳定状态,用以表示逻辑状…...
【网站架构部署与优化】web服务与http协议
文章目录 HTMLHTML 概述HTML 语法规则HTML 文件结构头标签中常用标签静态网页与动态网页1. 静态网页2. 动态网页3. 动态网页语言 HTTP协议概述主要的HTTP版本包括:HTTP方法GET与POST方法的比较 HTTP状态码分类及常见状态码HTTP常见状态码 HTTP 请求流程分析1. 请求报…...
【字符函数】strcpy函数(字符串复制函数)+strcat函数(字符串追加)+strcmp函数(字符串比较)【笔记】
1.复制函数--------------strcpy函数 函数使用 char*strcpy(char* destination, const char* source) strcpy函数用于拷贝字符串,即将一个字符串中的内容拷贝到另一个字符串中(会覆盖原字符串内容)。它的参数是两个指…...
codetop字符串刷题,刷穿地心!!不再畏惧!!暴打面试官!!
主要供自己回顾与复习,题源codetop标签字符串近半年,会不断更新 1.有效的括号字符串2.括号生成3.最长单词4.字符串转换整数(atoi)5.整数转罗马数字6.罗马数字转整数7.比较版本号8.最长公共前缀9.面试题17.15.最长单词10.验证IP地址11.面试题01.06.字符串…...
快速体验Linux发行版:DistroSea详解与操作指南
DistroSea 是一个功能强大的在线平台,允许用户在无需下载或安装的情况下,通过浏览器直接测试多种Linux和BSD发行版。该平台非常适合Linux爱好者、系统管理员和开发者,提供一个简便的方式来体验各种操作系统而无需影响本地设备。 为什么选择D…...
Java设计模式—面向对象设计原则(二) --------> 里氏代换原则 LSP (完整详解,附有代码+案列)
文章目录 里氏代换原则3.2.1 概述3.2.2 改进上述代码 里氏代换原则 里氏代换原则:Liskov Substitution Principle,LSP 3.2.1 概述 里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则:任何基类可以出现的地方,子类一定…...
使用ShardingSphere实现MySql的分库分表
目录 一 什么是ShardingSphere分库分表 二 代码实现 1.导入相关依赖 2.配置相关参数 3.创建学生类以及mapper接口 4.实现 StandardShardingAlgorithm接口自定义分片算法 唐洋洋我知道你在看!!!嘿嘿 一 什么是ShardingSphere分库分表 我们平时在设计数据库的时候…...
为什么 Feign 要用 HTTP 而不是 RPC?
一、引言 在现代微服务架构中,服务之间的通信是至关重要的环节。Feign 是一种常用的声明式 HTTP 客户端工具,它简化了服务间的调用过程。然而,在服务通信的领域中,除了基于 HTTP 的方式,还有 RPC(Remote Pr…...
OJ在线评测系统 前端开发设计优化通用菜单组件二 调试用户自动登录
通用的菜单组件开发二 接下来要完善 权限功能 就是只有登录后才能进入题目查看界面 用户只能看到我们有权限的菜单 我们要在路由文件里面去操作 原理是控制路由设置隐藏 只要用户没有权限 就过滤掉隐藏 全局权限管理 实现想清楚有那些权限 /*** 权限定义*/ const ACCES…...
mongodb 安装教程
mongodb 安装教程: https://blog.51cto.com/u_13646338/5449015 wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-5.0.9.tgz tar -zxvf mongodb-linux-x86_64-rhel70-5.0.9.tgz -C /opt/module/ [roothadoop102 module]# mv mongodb-linux-…...
Kerberos身份认证原理与实战排错指南
1. 为什么今天还要花时间搞懂 Kerberos?——一个被低估的“老协议”正在悄悄支撑着你的日常你每天登录公司内网查邮件、访问财务系统提交报销、用 Jenkins 构建代码、甚至在 Windows 域环境中打开一台同事的共享文件夹……这些看似顺滑的操作背后,大概率…...
用STM32CubeMX和HAL库快速上手WS2812B:告别手动计算延时,一键生成驱动框架
基于STM32CubeMX的WS2812B智能灯光控制:从零构建现代化驱动方案在智能硬件和物联网设备快速发展的今天,WS2812B可编程LED灯带因其丰富的色彩表现和简单的单线控制方式,成为创客和工程师们最喜爱的显示组件之一。然而,传统的寄存器…...
【CP-05】RTE运行时环境 - SWC的操作系统接口
CP-05_RTE运行时环境【CP-05】RTE运行时环境 - SWC的“操作系统接口”前言在AUTOSAR架构中,RTE(Runtime Environment,运行时环境)是一个常被提及却难以理解的概念。它像是应用层软件组件(SW-C)与底层基础软…...
Python基础语法:常用内置函数
round():四舍五入 # 省略 ndigits print(round(3.14)) # 输出 3(int) print(round(3.66)) # 输出 4# 指定 ndigits print(round(3.14159, 2)) # 输出 3.14(float) print(round(3.666, 2)) # 输出 3.67# …...
3步解锁网易云音乐NCM加密:让音乐真正属于你
3步解锁网易云音乐NCM加密:让音乐真正属于你 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为下载的网易云音乐只能在特定客户端播放而烦恼吗?当你精心收藏的歌曲被NCM格式"锁"在单一平台时&a…...
人类防伪指南:为什么你越写错字,HR越信你是真人?
前言各位码农、算法侠、CtrlC/V十级学者请注意:你有没有过这样的经历?辛辛苦苦肝了一晚上文档,逻辑严密、语法丝滑、连Markdown都对齐得像军训方阵,结果老板幽幽来一句:“这真是你自己写的?”那一刻&#x…...
Performance-Fish:让你的《环世界》后期游戏帧率提升400%的终极优化方案
Performance-Fish:让你的《环世界》后期游戏帧率提升400%的终极优化方案 【免费下载链接】Performance-Fish Performance Mod for RimWorld 项目地址: https://gitcode.com/gh_mirrors/pe/Performance-Fish 你是否曾在《环世界》游戏后期,面对庞大…...
高精度光照检测
光线检测仪,kotlin开发,调用手机感光模块检测室内外光照强度,用途多多,我主要用途孩子写作业检测光照保护视力。 食用方法∶打开即测,速度快,无广告,手机平视即可,无须直视光线。 买…...
用Python复现Nature论文:仅需100次循环数据,提前预测锂电池寿命(附完整代码与数据集)
用Python实战预测锂电池寿命:从数据特征到模型部署全解析锂电池作为现代能源存储的核心组件,其寿命预测一直是工业界和学术界关注的焦点。传统方法往往需要等待电池出现明显容量衰减才能进行判断,而最新研究表明,通过分析早期循环…...
树莓派工业GPIO接口板:电气隔离与电平转换实战指南
1. 项目概述:为什么需要一块工业级GPIO接口板?如果你用树莓派做过一些硬件项目,尤其是涉及到控制继电器、电机或者连接工业设备(比如PLC、变频器)时,大概率踩过这样的坑:直接用树莓派的GPIO引脚…...
