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());}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1539170.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Web开发:Thymeleaf模板引擎

1. Thymeleaf 简介 Thymeleaf 是一个现代的服务器端模板引擎&#xff0c;用于生成 HTML、XML、JavaScript 和 CSS。它的设计理念是使模板能够自然地在 Web 浏览器中呈现&#xff0c;同时允许动态生成内容。 2. 最佳实践总结 2.1 项目结构和模板组织 保持清晰的目录结构&…

Electron-vue asar 局部打包优化处理方案——绕开每次npm run build 超级慢的打包问题

背景 因为组员对于 Electron 打包过程存在比较迷糊的状态&#xff0c;且自己也没主动探索 Electron-vue 打包细节&#xff0c;导致每次打包过程都消耗 5-6 分钟的时间&#xff0c;在需要测试生产打包时&#xff0c;极其浪费时间&#xff0c;为此针对 Electron-vue 打包的几个环…

C++ —— 关于vector

目录 链接 1. vector的定义 2. vector的构造 3. vector 的遍历 4. vector 的扩容机制 5. vector 的空间接口 5.1 resize 接口 5.2 push_back 5.3 insert 5.4 erase 5.5 流插入与流提取 vector 并不支持流插入与流提取&#xff0c;但是可以自己设计&#xff0c;更…

MSF的使用学习

一、更新MSF apt update # 更新安装包信息&#xff1b;只检查&#xff0c;不更新&#xff08;已安装的软件包是否有可用的更新&#xff0c;给出汇总报告&#xff09; apt upgrade # 更新已安装的软件包&#xff0c;不删除旧包&#xff1b; apt full-upgrade # 升级包&#x…

深度学习-18-深入理解BERT实战使用预训练的DistilBERT模型

文章目录 1 预训练的BERT模型2.1 单词级的嵌入表示2.2 句子级的嵌入表示2.3 从最顶层编码器层中抽取嵌入表示2.3.1 预处理输入2.3.2 获得嵌入表示2.4 从所有的编码器层中抽取嵌入表示2.4.1 预处理输入2.4.2 嵌入表示3 为下游任务微调BERT3.1 文本分类3.1.1 原理(微调BERT模型)3…

MTK芯片机型的“工程固件” 红米note9 5G版资源预览 写入以及改写参数相关步骤解析

小米机型:小米5 小米5x 米6 米6x 米8 米9 米10系列 米11系列 米12系列 mix mix2 mix2s mix3 max max2 max3 note3 8se 9se cc9系列 米play 平板系列等分享 红米机型:红米note4 红米note4x 红米note5 红米note6 红米note7 红米note8 红米note8pro 红米s2 红米note7pro 红米…

大数据概念与价值

文章目录 引言大数据的概念高德纳咨询公司的定义麦肯锡全球研究所的定义什么是大数据&#xff1f; 大数据的特征Volume&#xff08;体积&#xff09;Variety&#xff08;种类&#xff09;Velocity&#xff08;速度&#xff09;Value&#xff08;价值&#xff09;Veracity&#…

OpenCV 1

前言&#xff1a;开新坑辽&#xff0c;&#xff0c; 目录 计算机眼中的图像 视频的读取与处理 ROI区域 边界填充 数值计算 腐蚀操作 膨胀操作 开运算与闭运算 梯度计算 礼貌与黑帽 Sobel算子 梯度计算方法 scharr与laplacian 计算机眼中的图像 灰色图片&#xff0…

微服务——网关路由(Spring Cloud Gateway)

网关路由 1.什么是网关 网关又称网间连接器、协议转换器&#xff0c;是在网络层以上实现网络互连的复杂设备&#xff0c;主要用于两个高层协议不同的网络之间的互连。网关就是网络的关口。数据在网络间传输&#xff0c;从一个网络传输到另一网络时就需要经过网关来做数据的路由…

MYSQL登录失败,确保密码正确,常见问题

今天登录MYSQL时&#xff0c;发现登录不进去,我能确保密码没有错误&#xff0c;并且我昨天以这样的方式登录成功&#xff0c;我已经重启过mysql服务&#xff0c;但是依旧登录不进去。 C:\Users\user>mysql -u root -p Enter password: ****** ERROR 1045 (28000): Access …

(已解决)vscode如何选择python解释器

文章目录 前言解决方案 前言 有的时候可能有不同版本的编译器&#xff0c;以适用不同年份的项目。所以&#xff0c;怎么在vscode中换python解释器呢&#xff1f; 解决方案 对着要运行的python文件进行右键&#xff0c;比如我是要运行main文件&#xff0c;点击那个命令选项版…

为什么7kw交流充电桩主板是充电桩运行的关键

7kw交流充电桩主板是电动汽车充电站中的一个核心组件&#xff0c;负责管理和控制充电过程。它是一种专门为7kw功率设计的交流充电设备的控制中枢&#xff0c;包含了电力电子、微处理器、通信模块等多种元件&#xff0c;以确保安全、高效地为电动汽车提供电能。 7kw与3.5kw主板的…

音视频入门基础:AAC专题(10)——FFmpeg源码中计算AAC裸流每个packet的pts、dts、pts_time、dts_time的实现

音视频入门基础&#xff1a;AAC专题系列文章&#xff1a; 音视频入门基础&#xff1a;AAC专题&#xff08;1&#xff09;——AAC官方文档下载 音视频入门基础&#xff1a;AAC专题&#xff08;2&#xff09;——使用FFmpeg命令生成AAC裸流文件 音视频入门基础&#xff1a;AAC…

【CSS in Depth 2 精译_034】5.4 Grid 网格布局的显示网格与隐式网格(下)

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一章 层叠、优先级与继承&#xff08;已完结&#xff09; 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位&#xff08;已完结&#xff09; 2.1 相对…

在 React 中模拟输入

需求 与 Bug 项目的 C# 桌面端使用 CefSharp 内嵌了一个三方网站&#xff0c;在外部实现了一个登录控件&#xff0c;外部登录后希望内嵌的三方网站自动登录&#xff0c;实现代码如下&#xff1a; browser.ExecuteScriptAsync($"document.getElementsByName(username)[0]…

Etcd权限认证管理

1 查看是否开启权限认证 ctl auth status 2 开启权限认证 ctl auth enable。开启后每一条命令都要加上用户 --userroot:root(root默认最高权限) 3 创建其他用户 ctl user add user1 --user用户名:密码 4 创建角色 ctl role add testR --user 5 为角色添加权限 ctl role g…

Linux基础命令——文件系统的日常管理

目录 一.如何查看当前工作目录?&#xff08;你现在所处的位置路径&#xff09; 二.命令touch的用途是什么?还有别的方法新建文件吗? &#xff08;1&#xff09;创建空文件 &#xff08;2&#xff09;如果已经存在这个文件&#xff0c;就会更新创建时间。 &#xff08;3…

优化器与现有网络模型的修改

文章目录 一、优化器是什么二、优化器的使用三、分类模型VGG16四、现有网络模型的修改 一、优化器是什么 优化器&#xff08;Optimizer&#xff09;是一个算法&#xff0c;用于在训练过程中调整模型的参数&#xff0c;以便最小化损失函数&#xff08;Loss Function&#xff09…

【论文阅读笔记】YOLOv10: Real-Time End-to-End Object Detection

论文地址&#xff1a;https://arxiv.org/abs/2405.14458 文章目录 论文小结论文简介论文方法为NMS-free训练的一致性双标签分配双标签分配一致性匹配度量 效率-精度整体驱动的模型设计效率驱动模型设计轻量级分类检测头Spatial-channel 解耦下采样Rank-guided block design 精度…