当前位置: 首页 > news >正文

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类似下面的写法&#xff1a; SpringBootTest class SysAuthServiceTest {AutowiredSysRoleAuthMapper sysRoleAuthMapper;Testpublic void test() {QueryWrapper<SysRoleAuth> queryWrapper new QueryWrapper<&g…...

【jupyter notebook】环境部署及pycharm连接虚拟机和本地两种方式

Python数据处理分析简介 Python作为当下最为流行的编程语言之一 可以独立完成数据分析的各种任务数据分析领域里有海量开源库机器学习/深度学习领域最热门的编程语言在爬虫&#xff0c;Web开发等领域均有应用 与Excel&#xff0c;PowerBI&#xff0c;Tableau等软件比较 Excel有…...

TypeScript异常处理

1.异常的概念 程序运行中意外发生的情况就成为异常 例子&#xff1a; //除法运算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是输出的高&#xff0c;h是输入的高&#xff0c;k_h是卷积核的高 w类似stride1 h h - k_h padding*2 1通用公式 stride1就是上面的公式 h (h - k_w 2*padding stride)//stride 一些常见的卷积 高宽不变的卷积&#xff1a;kernel…...

vue3+ts 使用amCharts展示地图,1.点击左侧国家,可以高亮并放大右侧地图对应的国家。 2.展示数据球。

效果图展示&#xff1a; 1.点击左侧国家&#xff0c;可以高亮并放大右侧地图对应的国家。 2.展示数据球。 下载依赖 yarn add amcharts/amcharts5其中&#xff0c;props.countryData的数据格式为 [{ “country”: “加拿大”, “code”: “CA”, “deviceCount”: 1 },{ “c…...

汽车无钥匙启动功能工作原理

移‌动管家无钥匙启动‌是一种科技化的汽车启动方式&#xff0c;它允许车主在不使用传统钥匙的情况下启动车辆。这种技术通过智能感应系统实现&#xff0c;车主只需携带智能钥匙&#xff0c;当靠近车辆时&#xff0c;车辆能够自动解锁并准备启动。启动车辆时&#xff0c;车主无…...

C++标准的一些特性记录:C++11的auto和decltype

文章目录 auto容器遍历配合lambda表达式decltype两者对引用类型的处理是相同的decltype保留const,而auto不会保留const在C++11中,引入了两个新的关键字,auto和decltype两个关键字,都是用于做类型推断。但是使用的场景有些区别。 auto 容器遍历 auto这个关键字,我个人在编…...

【Elasticsearch系列四】ELK Stack

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

【新手上路】衡石分析平台使用手册-认证方式

认证方式​ 用户登录衡石系统时&#xff0c;系统需要对输入的用户名和密码进行验证&#xff0c;保证系统的安全。衡石提供 CAS、SAML2、OAUTH2等多种单点登录认证方式。在 SSO 单点登录中&#xff0c;衡石是服务提供者 SP&#xff08;Service Provider&#xff09;为用户提供所…...

数字电路与逻辑设计-触发器功能测试及其应用

一、实验目的 1&#xff0e;验证基本RS、JK、D、T和T’触发器的逻辑功能及使用方法&#xff1b; 2&#xff0e;能进行触发器之间的相互转换&#xff1b; 3&#xff0e;学习触发器的一些应用。 二、实验原理 触发器具有两个能够自行保持的稳定状态&#xff0c;用以表示逻辑状…...

【网站架构部署与优化】web服务与http协议

文章目录 HTMLHTML 概述HTML 语法规则HTML 文件结构头标签中常用标签静态网页与动态网页1. 静态网页2. 动态网页3. 动态网页语言 HTTP协议概述主要的HTTP版本包括&#xff1a;HTTP方法GET与POST方法的比较 HTTP状态码分类及常见状态码HTTP常见状态码 HTTP 请求流程分析1. 请求报…...

【字符函数】strcpy函数(字符串复制函数)+strcat函数(字符串追加)+strcmp函数(字符串比较)【笔记】

1.复制函数--------------strcpy函数 函数使用 char*strcpy&#xff08;char* destination, const char* source&#xff09; strcpy函数用于拷贝字符串&#xff0c;即将一个字符串中的内容拷贝到另一个字符串中&#xff08;会覆盖原字符串内容&#xff09;。它的参数是两个指…...

codetop字符串刷题,刷穿地心!!不再畏惧!!暴打面试官!!

主要供自己回顾与复习&#xff0c;题源codetop标签字符串近半年&#xff0c;会不断更新 1.有效的括号字符串2.括号生成3.最长单词4.字符串转换整数(atoi)5.整数转罗马数字6.罗马数字转整数7.比较版本号8.最长公共前缀9.面试题17.15.最长单词10.验证IP地址11.面试题01.06.字符串…...

快速体验Linux发行版:DistroSea详解与操作指南

DistroSea 是一个功能强大的在线平台&#xff0c;允许用户在无需下载或安装的情况下&#xff0c;通过浏览器直接测试多种Linux和BSD发行版。该平台非常适合Linux爱好者、系统管理员和开发者&#xff0c;提供一个简便的方式来体验各种操作系统而无需影响本地设备。 为什么选择D…...

Java设计模式—面向对象设计原则(二) --------> 里氏代换原则 LSP (完整详解,附有代码+案列)

文章目录 里氏代换原则3.2.1 概述3.2.2 改进上述代码 里氏代换原则 里氏代换原则&#xff1a;Liskov Substitution Principle&#xff0c;LSP 3.2.1 概述 里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则&#xff1a;任何基类可以出现的地方&#xff0c;子类一定…...

使用ShardingSphere实现MySql的分库分表

目录 一 什么是ShardingSphere分库分表 二 代码实现 1.导入相关依赖 2.配置相关参数 3.创建学生类以及mapper接口 4.实现 StandardShardingAlgorithm接口自定义分片算法 唐洋洋我知道你在看!!!嘿嘿 一 什么是ShardingSphere分库分表 我们平时在设计数据库的时候&#xf…...

为什么 Feign 要用 HTTP 而不是 RPC?

一、引言 在现代微服务架构中&#xff0c;服务之间的通信是至关重要的环节。Feign 是一种常用的声明式 HTTP 客户端工具&#xff0c;它简化了服务间的调用过程。然而&#xff0c;在服务通信的领域中&#xff0c;除了基于 HTTP 的方式&#xff0c;还有 RPC&#xff08;Remote Pr…...

OJ在线评测系统 前端开发设计优化通用菜单组件二 调试用户自动登录

通用的菜单组件开发二 接下来要完善 权限功能 就是只有登录后才能进入题目查看界面 用户只能看到我们有权限的菜单 我们要在路由文件里面去操作 原理是控制路由设置隐藏 只要用户没有权限 就过滤掉隐藏 全局权限管理 实现想清楚有那些权限 /*** 权限定义*/ const ACCES…...

mongodb 安装教程

mongodb 安装教程&#xff1a; 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-…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)

笔记整理&#xff1a;刘治强&#xff0c;浙江大学硕士生&#xff0c;研究方向为知识图谱表示学习&#xff0c;大语言模型 论文链接&#xff1a;http://arxiv.org/abs/2407.16127 发表会议&#xff1a;ISWC 2024 1. 动机 传统的知识图谱补全&#xff08;KGC&#xff09;模型通过…...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

Android15默认授权浮窗权限

我们经常有那种需求&#xff0c;客户需要定制的apk集成在ROM中&#xff0c;并且默认授予其【显示在其他应用的上层】权限&#xff0c;也就是我们常说的浮窗权限&#xff0c;那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

在WSL2的Ubuntu镜像中安装Docker

Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包&#xff1a; for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

如何更改默认 Crontab 编辑器 ?

在 Linux 领域中&#xff0c;crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用&#xff0c;用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益&#xff0c;允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...

FFmpeg:Windows系统小白安装及其使用

一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】&#xff0c;注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录&#xff08;即exe所在文件夹&#xff09;加入系统变量…...