SpringBoot 单元测试利器-Mockito

news2025/4/7 20:58:28

Mockito 是一种 Java mock 框架,他主要是用来做 mock 测试的,他可以模拟任何 Spring 管理的 bean、模拟方法的返回值、模拟抛出异常...等,在了解 Mockito 的具体用法之前,得先了解什麽是 mock 测试

1. 什么是 mock 测试?

mock 测试就是在测试过程中,创建一个假的对象,避免你为了测试一个方法,却要自行构建整个 bean 的依赖链

像是以下这张图,类 A 需要调用类 B 和类 C,而类 B 和类 C 又需要调用其他类如 D、E、F 等,假设类 D 是一个外部服务,那就会很难测,因为你的返回结果会直接的受外部服务影响,导致你的单元测试可能今天会过、但明天就过不了了

而当我们引入 mock 测试时,就可以创建一个假的对象,替换掉真实的 bean B 和 C,这样在调用B、C的方法时,实际上就会去调用这个假的 mock 对象的方法,而我们就可以自己设定这个 mock 对象的参数和期望结果,让我们可以专注在测试当前的类 A,而不会受到其他的外部服务影响,这样测试效率就能提高很多

2. Mockito 简介

说完了 mock 测试的概念,接下来我们进入到今天的主题,Mockito

Mockito 是一种 Java mock 框架,他主要就是用来做 mock 测试的,他可以模拟任何 Spring 管理的 bean、模拟方法的返回值、模拟抛出异常...等,他同时也会记录调用这些模拟方法的参数、调用顺序,从而可以校验出这个 mock 对象是否有被正确的顺序调用,以及按照期望的参数被调用

像是 Mockito 可以在单元测试中模拟一个 service 返回的数据,而不会真正去调用该 service,这就是上面提到的 mock 测试精神,也就是通过模拟一个假的 service 对象,来快速的测试当前我想要测试的类

目前在 Java 中主流的 mock 测试工具有 Mockito、JMock、EasyMock..等,而 SpringBoot 目前内建的是 Mockito 框架

题外话说一下,Mockito 是命名自一种调酒莫吉托(Mojito),外国人也爱玩谐音梗。。。

3. 在 SpringBoot 单元测试中使用 Mockito

首先在 pom.xml 下新增 spring-boot-starter-test 依赖,该依赖内就有包含了 JUnit、Mockito

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

先写好一个 UserService,他里面有两个方法 getUserById() 和 insertUser(),而他们会分别去再去调用 UserDao 这个 bean的 getUserById() 和 insertUser() 方法

@Component
publicclass UserService {
    
    @Autowired
    private UserDao userDao;

    public User getUserById(Integer id) {
        return userDao.getUserById(id);
    }

    public Integer insertUser(User user) {
        return userDao.insertUser(user);
    }
}

User model 的定义如下

publicclass User {
    private Integer id;
    private String name;
    //省略 getter/setter
}

如果这时候我们先不使用 Mockito 模拟一个假的 userDao bean,而是真的去调用一个正常的 Spring bean 的 userDao 的话,测试类写法如下。其实就是很普通的注入 userService bean,然后去调用他的方法,而他会再去调用 userDao 取得数据库的数据,然后我们再对返回结果做 assert 断言检查

@RunWith(SpringRunner.class)
@SpringBootTest
publicclass UserServiceTest {

    //先普通的注入一个userService bean
    @Autowired
    private UserService userService;

    @Test
    public void getUserById() throws Exception {
        //普通的使用userService,他里面会再去调用userDao取得数据库的数据
        User user = userService.getUserById(1);
        
        //检查结果
        Assert.assertNotNull(user);
        Assert.assertEquals(user.getId(), new Integer(1));
        Assert.assertEquals(user.getName(), "John");
    }
}

但是如果 userDao 还没写好,又想先测 userService 的话,就需要使用 Mockito 去模拟一个假的 userDao 出来

使用方法是在 userDao 上加上一个 @MockBean 注解,当 userDao 被加上这个注解之后,表示 Mockito 会帮我们创建一个假的 mock 对象,替换掉 Spring 中已存在的那个真实的 userDao bean,也就是说,注入进 userService 的 userDao bean,已经被我们替换成假的 mock 对象了,所以当我们再次调用 userService 的方法时,会去调用的实际上是 mock userDao bean 的方法,而不是真实的 userDao bean

当我们创建了一个假的 userDao 后,我们需要为这个 mock userDao 自定义方法的返回值,这里有一个公式用法,下面这段代码的意思为,当调用了某个 mock 对象的方法时,就回传我们想要的自定义结果

Mockito.when( 对象.方法名() ).thenReturn( 自定义结果 )

使用 Mockito 模拟 bean 的单元测试具体实例如下

@RunWith(SpringRunner.class)
@SpringBootTest
publicclass UserServiceTest {
    
    @Autowired
    private UserService userService;
    
    @MockBean
    private UserDao userDao;

    @Test
    public void getUserById() throws Exception {
        // 定义当调用mock userDao的getUserById()方法,并且参数为3时,就返回id为200、name为I'm mock3的user对象
        Mockito.when(userDao.getUserById(3)).thenReturn(new User(200, "I'm mock 3"));
        
        // 返回的会是名字为I'm mock 3的user对象
        User user = userService.getUserById(1);

        Assert.assertNotNull(user);
        Assert.assertEquals(user.getId(), new Integer(200));
        Assert.assertEquals(user.getName(), "I'm mock 3");
    }
}

Mockito 除了最基本的 Mockito.when( 对象.方法名() ).thenReturn( 自定义结果 ),还提供了其他用法让我们使用

thenReturn 系列方法

当使用任何整数值调用 userService 的 getUserById() 方法时,就回传一个名字为 I'm mock3 的 user 对象

Mockito.when(userService.getUserById(Mockito.anyInt())).thenReturn(new User(3, "I'm mock"));
User user1 = userService.getUserById(3); // 回传的user的名字为I'm mock
User user2 = userService.getUserById(200); // 回传的user的名字也为I'm mock

限制只有当参数的数字是 3 时,才会回传名字为 I'm mock 3 的 user 对象

Mockito.when(userService.getUserById(3)).thenReturn(new User(3, "I'm mock"));
User user1 = userService.getUserById(3); // 回传的user的名字为I'm mock
User user2 = userService.getUserById(200); // 回传的user为null

当调用 userService 的 insertUser() 方法时,不管传进来的 user 是什麽,都回传 100

Mockito.when(userService.insertUser(Mockito.any(User.class))).thenReturn(100);
Integer i = userService.insertUser(new User()); //会返回100

thenThrow 系列方法

当调用 userService 的 getUserById() 时的参数是 9 时,抛出一个 RuntimeException

Mockito.when(userService.getUserById(9)).thenThrow(new RuntimeException("mock throw exception"));
User user = userService.getUserById(9); //会抛出一个RuntimeException

如果方法没有返回值的话(即是方法定义为public void myMethod() {...}),要改用 doThrow() 抛出 Exception

Mockito.doThrow(new RuntimeException("mock throw exception")).when(userService).print();
userService.print(); //会抛出一个RuntimeException

verify 系列方法

检查调用 userService 的 getUserById()、且参数为3的次数是否为1次

Mockito.verify(userService, Mockito.times(1)).getUserById(Mockito.eq(3)) ;

验证调用顺序,验证 userService 是否先调用 getUserById() 两次,并且第一次的参数是 3、第二次的参数是 5,然后才调用insertUser() 方法

InOrder inOrder = Mockito.inOrder(userService);
inOrder.verify(userService).getUserById(3);
inOrder.verify(userService).getUserById(5);
inOrder.verify(userService).insertUser(Mockito.any(User.class));

4. Mockito 的限制

上述就是 Mockito 的 mock 对象使用方法,不过当使用 Mockito 在 mock 对象时,有一些限制需要遵守

  • 不能 mock 静态方法

  • 不能 mock private 方法

  • 不能 mock final class

因此在写代码时,需要做良好的功能拆分,才能够使用 Mockito 的 mock 技术,帮助我们降低测试时 bean 的耦合度

5. 总结

Mockito 是一个非常强大的框架,可以在执行单元测试时帮助我们模拟一个 bean,提高单元测试的稳定性

并且大家可以尝试在写代码时,从 mock 测试的角度来写,更能够写出功能切分良好的代码架构,像是如果有把专门和外部服务沟通的代码抽出来成一个 bean,在进行单元测试时,只要透过 Mockito 更换掉那个 bean 就行了

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

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

相关文章

Java Long对象对比,用equals函数

文章目录一、前文二、调试三、后记一、前文 同事问我一个问题&#xff0c;我咋一看也愣住了 代码&#xff1a; if(ObjectUtil.isNotNull(temp.getUserId()) && temp.getUserId()!SecurityUtils.getUserId()) {logger.error(temp.toString());logger.error("Securi…

ICV:车规级激光雷达市场规模超7亿美元,补盲雷达有望2024年量产

全球前沿科技咨询机构ICV近期发布了车载激光雷达的市场分析报告&#xff0c;ICV在报告中表示激光雷达是自动驾驶传感器中增速最快的传感器&#xff0c;预计未来五年的复合年增长率达43.4%。此外&#xff0c;混合固态式激光雷达近五年内将仍以应用转镜技术为主。 本报告旨在评估…

【store商城项目05】新增收获地址的开发

新增收获地址的开发1.创建地址表2.创建实体类3.持久层的开发3.1规划需要执行的SQL语句3.2Mapper接口与抽象方法3.3配置SQL映射3.4测试4.业务层的开发4.1规划异常4.2接口和抽象方法4.3实现抽象方法4.4测试5.控制层的开发5.1规划异常5.2设计请求5.3处理请求5.4测试6前端页面1.创建…

【Linux】Linux权限(二)默认权限的来源

默认权限1.默认权限2.默认权限&#xff08;最终权限&#xff09;由谁决定2.1起始权限2.2umask&#xff08;权限掩码&#xff09;2.3 最终权限的计算3.总结1.默认权限 在Linux下&#xff0c;我们以普通用户创建一个普通文件&#xff0c; 这个普通文件(这里不包括可执行)的默认权…

萤石网络IPO首日破发:市值缩水20亿元,海康威视为控股股东

12月28日&#xff0c;杭州萤石网络股份有限公司&#xff08;下称“萤石网络”&#xff0c;SH:688475&#xff09;在上海证券交易所科创板上市。本次上市&#xff0c;萤石网络的发行价为28.77元/股&#xff0c;发行1.13亿股&#xff0c;募资总额为32.51亿元&#xff0c;总市值约…

【1.1】认识微服务--服务架构演变

认识微服务--服务架构演变单体架构分布式架构服务治理微服务微服务架构特征小结知识内容来自于黑马程序员视频教学和百度百科。博主仅作笔记整理便于回顾学习。如有侵权请私信我。 单体架构 单体架构&#xff1a; 将业务的所有功能集中在一个项目中开发&#xff0c;打成一个包…

线性规划求解-MATLAB Lingo Python实现

线性规划求解-MATLAB Lingo Python实现 线性规划 线性规划是辅助人们进行科学管理的一种数学方法&#xff0c;是研究线性约束条件下线性目标函数的极值问题的数学理论和方法。其展开形式可以表示为如下形式&#xff1a; Max⁡(Min⁡)zc1x1c2x2…cnxns.t. {a11x1a12x2⋯a1nxn≥…

2022年度十大科学突破榜单出炉!

科学的进步在很大程度上推动着人类社会的发展。而了解最新年度科学突破&#xff0c;有助于引领我们知晓世界科学进展&#xff0c;看清楚未来方向。故此&#xff0c;知识人网小编本期介绍《科学》杂志最新公布的2022年科学突破榜单。 12月16日&#xff0c;美国《科学》杂志网站列…

一种时间复杂度为O(2ⁿ)、空间复杂度为O(n)的子集和问题的算法

子集和问题&#xff08;Subset-Sum Problem, SSP&#xff09;是说给定一个自然数集合S{a1,a2,⋯,an}S\{a_1,a_2,\cdots,a_n\}S{a1​,a2​,⋯,an​}&#xff0c;它含有nnn个元素&#xff0c;现在又给定一个自然数sss&#xff0c;问是否存在SSS的一个子集TTT使得TTT的所有元素之和…

JavaScript 隐秘者 | Console.xxx竟然如此好用

JavaScript 隐秘者 | Console.xxx竟然如此好用 文章目录JavaScript 隐秘者 | Console.xxx竟然如此好用一、控制台调试二、对象方法 &#x1f356;1)、.assert() 条件断言2)、.clear() 清空控制台3)、.count() 计算调用数 ⭕4)、.countReset() 重置计数器5)、.debug() 调试消息6…

文献翻译 (3):非支配排序遗传算法 (Non-dominated Sorting Genetic Algorithm, NSGA-II)

文章目录1 引入2 多目标优化3 更多的定义3.1 支配3.2 非支配集3.3 全局Pareto最优集4 NSGA-II1 引入 本文主要介绍多目标优化的基本概念以及NSGA-II。 2 多目标优化 多目标优化的优化目标之间存在一定的冲突&#xff0c;例如一个目标增长&#xff0c;导致另一个减少。因此这…

Vue2 新手上路无处不在的特殊符号,让人傻傻分不清 “:”、“.”、“@”、“#” 、“{{}}“ 、“$“

刚刚学vue没多久&#xff0c;经常分不清情况什么时候用什么符号&#xff1a; “:” 是指令 “v-bind”的缩写 “.”是修饰符 “”是指令“v-on”的缩写 &#xff0c;它用于监听 DOM 事件 “#”是v-slot的缩写&#xff1b; "{{}}" 插值语法 "$" &#…

智慧工厂在线云平台,助力企业降本增效!

随着传统制造企业规模的扩大&#xff0c;人工管理方法不可避免的产生延时、错误和矛盾&#xff0c;人工管理方法已经无法对生产管理实施有效的控制, 随着设备类型、数量不断增加&#xff0c;人工管理的方式已经无法满足生产过程中人、机、料、法、环、测的有效管理。如何将这些…

2022年——一个老老老程序员的杭州折腾之旅

&#x1f4e2;欢迎点赞 &#xff1a;&#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff0c;赐人玫瑰&#xff0c;手留余香&#xff01;&#x1f4e2;本文作者&#xff1a;由webmote 原创&#x1f4e2;作者格言&#xff1a;无尽的折腾后&#xff0c;终于又回到…

[Android]视图的控触操作-MotionEvent

引入 对屏幕的任何操作&#xff0c;系统都会创建一个触摸事件的对象MotionEvent来应对这个操作。当点击手机屏幕的某一个视图时&#xff0c;最先感应到的是屏幕&#xff0c;因为Activity系统是分层的结构&#xff0c;底层是一些驱动&#xff0c;所以驱动就会得到信息并且把信息…

分布式共识算法——Paxos、ZAB、Raft

分布式算法 01 分布式基本理论 CAP理论 1998年&#xff0c;加州大学的计算机科学家 Eric Brewer 提出&#xff0c;分布式系统有三个指标。 一致性&#xff08;C&#xff09;&#xff1a;在分布式系统中的所有数据备份&#xff0c;在同一时刻是否同样的值&#xff0c;即写操…

第008课 - linux安装docker

文章目录 linux安装docker安装docker启动docker检查dockerdocker设置开机自启动linux安装docker 每一种容器都是一个完整的运行环境,容器之间互相隔离的。 windows的ghost工具就是类似docker。 从网上获取镜像,基于镜像,docker可以启动一个容器。 所以,我们以后想要装某种…

elasticsearch在linux环境安装遇到问题

es在linux环境安装遇到问题 1、启动失败日志 ERROR: [1] bootstrap checks failed [1]: the default discovery settings are unsuitable for production use; at least one of [discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be con…

对某颜色站的一次渗透实战

1. 前提 1.1 某颜色cms弱口令原理 去年的时候&#xff0c;在网上看到有些师傅在打击颜色站&#xff0c;当时自己也摸索着试试&#xff0c;利用一个叫做jiuse cms的站来进行批量测试。 这个cms的特点是非常的小&#xff0c;前台除了xss之外&#xff0c;基本上漏洞都在后台&…

区块链+游戏:未来真的有未来吗?

今年以来&#xff0c;伴随着元宇宙与P2E的火热浪潮&#xff0c;区块链以其强大的叙事能力势如破竹的切入到传统游戏领域&#xff0c;顶级风投、知名巨企纷纷入局&#xff0c;迸发出极大的经济潜能&#xff0c;引发了社会的广泛关注。 一方面&#xff0c;区块链技术在游戏的运用…