Mockito单元测试Mockito对Service层的测试案例

前言

以下是关于Mockito的API使用文档

官网:http://mockito.org/
官网英文API文档:https://javadoc.io/static/org.mockito/mockito-core/5.10.0/help-doc.html#index
非官方中文API文档:https://gitee.com/wnboy/mockito-doc-zh#mockito-%E4%B8%AD%E6%96%87%E6%96%87%E6%A1%A3–2026-beta-

单元测试

在这里插入图片描述单元测试(Unit Testing)是软件开发过程中的一种基本测试方法,它针对程序中的最小可独立测试的单元进行验证。这个“单元”通常是指代码中最基本的模块化结构,如函数、方法或类。在面向对象编程中,一个单元可以是一个单一的方法;在过程式编程中,它可以是一个单独的函数。

在这里插入图片描述在单元测试中,我们要保证我们的测试环境是独立的,不需要借助于数据库、外部文件等,我们关心的就只有实际运行代码的逻辑是否正确。所以单元测试更多我个人认为侧重点应该是代码层面。

在这里插入图片描述那么问题来了,单元测试既然需要独立不依赖于外部数据,那么在实际开发中,对于Service层来说,大部分都是得触及数据库,那么对于这种情况该怎么解决问题呢???

在这里插入图片描述答案很明显,就是本期要介绍的Mockito测试框架


Mockito基本介绍

在这里插入图片描述Mockito是一个流行的Java单元测试框架,专门用于模拟(mocking)和 stubbing(预设行为)。在软件开发的测试阶段,Mockito允许开发者创建并配置模拟对象来替代真实依赖项,这样可以在隔离的环境中对特定代码片段进行单元测试,而无需关心那些依赖对象的具体实现或外部服务的状态。

在这里插入图片描述所以在单元测试中,对于Service层的触及数据库的那一个部分,我们可以使用Mockito提供的工具,将那些访问数据库甚至说模仿请求的部分方法或者代码给模拟欺骗过去!!!也就说,Mocktio可以帮我们假装访问过数据库等需要外部资源的操作,而不需要真实去触及那部分数据

在这里插入图片描述所以想要做好单元测试只会使用一个Junit是不够的,在成为测试之神的路上还得不断学习… Anyway,我们继续往下看…


Mockito必要配置

依赖导入

使用Mockito需要导入相关依赖

<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><scope>test</scope>
</dependency>

必要注解

我们需要在测试类上添加下面三个注解
@SpringBootTest
@RunWith(MockitoJUnitRunner.class)
@TestExecutionListeners(listeners = MockitoTestExecutionListener.class)

@SpringBootTest
注解用于标记测试类,表示该类是一个Spring Boot应用程序的测试类。
@RunWith(MockitoJUnitRunner.class)
注解用于指定测试运行器,使用Mockito框架的JUnit运行器。
@TestExecutionListeners(listeners = MockitoTestExecutionListener.class)
注解用于指定测试执行监听器,使用Mockito框架的测试执行监听器。


Mockito构造虚拟对象

在这里插入图片描述因为Mockito是一个模拟工具,所以我们在单元测试中,如果是触及外部资源的方法类,我们最好是通过Mockito去构造出对应的虚拟对象,这样我们的Mockito才有更多的操作手段。

而构造这样一个虚拟对象的方法如下:

  1. mock(Class classToMock))方法
  2. 注解
    • @Mock注解
    • @Spy注解
    • @InjectMocks注解(主要是为了打桩,后面介绍)

mock(Class classToMock)

我们使用方法构造时,只需要将class对象传入方法中即可,如下

Fans fans = mock(Fans.class);

注解(@InjectMocks、@Mock、@Spy)

注解类的方式也很简单,就类似我们使用@Autowired一样,在对象属性上添加注解就好了

@InjectMocks
private FansServiceImpl fansService;@Mock
private FansMapper fansMapper;@spy
private Fans fans;

在Mockito框架中,@InjectMocks@Mock@Spy 是用于创建模拟对象(mocks)和部分真实行为的对象(spies)的注解,并将它们注入到被测试类中的依赖关系中。下面是这三个注解的区别:

  1. @Mock

    • 使用此注解来创建一个模拟对象。当用此注解标记一个字段时,Mockito 会为该类型创建一个 mock 实例。
    • 模拟对象不执行任何实际的方法实现,而是允许你定义它们的行为(即方法调用时返回什么值或者抛出什么异常等)。
    • 对于 mock 对象,除非你明确配置了行为,否则所有方法默认都会返回默认值或空集合。
  2. @Spy

    • 使用 @Spy 注解可以创建一个“间谍”对象,它本质上是一个部分真实的对象。
    • spy 是原始对象的一个包装,它默认情况下会调用真实对象的方法,但在必要时也可以对某些方法进行模拟(stubbing)。
    • 这意味着当你想要跟踪或修改某个特定方法的行为时,同时保持其他方法的真实功能,就可以使用 spy。
  3. @InjectMocks

    • 当你有一个复杂的类结构,需要在一个类中注入多个依赖项(可能是 mock 或 spy)时,使用 @InjectMocks 注解。
    • 此注解标注在你想实例化的被测试类上,Mockito 将自动创建这个类的实例,并尝试通过构造函数或 setter 方法将所有带有 @Mock@Spy 注解的字段注入到这个实例中。
    • 目的是为了构建一个具有部分或全部模拟依赖项的被测对象,从而可以在单元测试中只关注被测类本身的逻辑。

总结来说,在 Mockito 测试场景中,通常会结合使用这些注解:

  • 如果你需要完全控制一个依赖对象的行为,就使用 @Mock 创建纯模拟对象。
  • 如果希望大部分时候依赖对象表现其真实行为,仅针对某些情况模拟,那么使用 @Spy
  • @InjectMocks 则用于装配整个被测试对象及其依赖关系,使得在测试过程中能够专注于被测试类的功能验证,而不必手动管理各个依赖项的注入。

Mockito的一些简单方法

方法预览图
在这里插入图片描述

when

when() 方法主要是用来捕捉虚拟对象的动作,当虚拟对象做出指定动作时,触发前置或者后置任务。

when有两种比较常用的操作,分别是doReturn().when()when().thenReturn()

doReturn().when()when().thenReturn() 在 Mockito 中都用于预设模拟对象(mock)的方法调用返回值,但它们在某些特定场景下有微妙的区别:

  1. when().thenReturn()

    • 这是Mockito中最常见的 stubbing 方法。
    • 它直接设置指定方法调用时的返回值。
    • 当使用 when().thenReturn() 时,如果该方法已经被调用过,则不会应用新的存根规则。也就是说,它仅对后续的调用生效。(也就是说,这个规则只要你不修改,后续都是这个返回结果,但是你后续修改了规则,也不会影响你之前调用产生的结果。

    示例:

    List ist = Mockito.mock(List.class);
    when(ist.get(0)).thenReturn("first"); // 设置 get(0) 的返回值为 "first"
    
  2. doReturn().when()

    • doReturn() 是一种更底层、更灵活的stubbing方式,它允许你在任何情况下改变方法的返回值,无论方法是否已经被调用过。
    • 使用 doReturn().when() 可以多次stub同一个方法,并且不论之前该方法是否被调用过,都会立即生效。
    • 特别地,当与条件匹配器结合使用时,doReturn().when() 更强大,因为它可以定义基于不同条件的不同返回行为。

    示例:

    List list = Mockito.mock(List.class);
    doReturn("first").when(list).get(0); // 即使 get(0) 已经被调用过,也会立即生效// 或者带有条件的 stubbing
    doReturn("value if empty").when(list).get(anyInt()).isNull(); // 如果 get 的参数为空,则返回 "value if empty"
    

总结起来,在大多数情况下,二者都可以用来设置方法调用的返回值。然而,doReturn().when() 提供了更多的灵活性,特别是在需要多次或条件性地改变一个方法的返回值时。而 when().thenReturn() 更加直观简洁,适用于大部分简单的存根需求。

any

在Mockito框架中,any() 方法是一个非常常用的参数匹配器(Argument Matcher),它允许我们在验证方法调用或者设置方法行为时对传入的参数进行灵活匹配。当使用 any() 时,Mockito并不会检查具体的参数值,而是表示“任何”类型的参数都可接受。

例如:

// 设置mock对象的行为,anyInt=>不关心具体传入的int参数是多少
when(list.get(Mockito.anyInt())).thenReturn("A mock value");// 验证方法调用,anyString=>不关心具体传入的String参数是什么
verify(list).add(Mockito.anyString());

在上述例子中,

  • list.get(Mockito.anyInt()) 表示对于mockedList对象的get方法,不论传入的是任意整数值,都将返回预设的"mock value"。
  • Mockito.verify(list).add(Mockito.anyString()) 则是验证list对象是否在其上调用了add方法,并且传入了一个字符串参数,而不在乎这个字符串参数的具体内容是什么。

从Java 8开始,由于类型推断的改进,可以直接使用无参数版本的 any() 方法,编译器会根据上下文自动推断出需要匹配的参数类型:

Mockito.when(list.get(anyInt())).thenReturn("A mock value");
Mockito.verify(list).add(anyString());

除了 any(),Mockito还提供了针对不同类型的 anyXxx() 方法,如 anyString(), anyList(), any(Class<T>) 等,它们都是用来做相应类型的参数匹配。

verify

在Mockito框架中,verify() 是一个非常关键的方法,用于验证模拟对象(mocks)是否按照预期的方式被调用了。通过 verify() 方法,你可以检查在测试执行过程中,某个模拟对象的特定方法是否被调用,以及调用的具体次数和参数。

例如:

// 创建并配置一个模拟对象
List list = Mockito.mock(List.class);
list.add("one");// 验证add方法是否被调用了一次,并且传入的是"one"
Mockito.verify(list).add("one");// 验证add方法是否被调用了任意次数,并且传入的是任何字符串
Mockito.verify(list).add(Mockito.anyString());// 验证add方法是否被调用了两次
Mockito.verify(list, times(2)).add(Mockito.any());

在上述示例中,

  • Mockito.verify(list).add("one") 检查 list 是否被调用了 add("one")
  • Mockito.verify(list, times(2)).add(Mockito.any()) 检查 listadd 方法是否被调用了两次,无论添加什么参数。

使用 verify() 方法有助于确保你的代码按预期与依赖项交互,从而提高测试覆盖率和代码质量。

atxxx

这些方法是Mockito框架中的验证调用次数的工具,它们与 verify() 方法结合使用来检查模拟对象(mock)的方法是否按照预期的次数被调用。

  1. atLeast()

    Mockito.verify(mockedList, atLeast(2)).clear();
    

    这行代码会验证 mockedList.clear() 方法至少被调用了两次。如果实际调用次数少于2次,验证将失败。

  2. atLeastOnce()

    Mockito.verify(list, atLeastOnce()).clear();
    

    这个简写形式表示至少调用一次。它等同于 atLeast(1),用于验证 list.clear() 方法至少被调用了一次。

  3. atMost(1)

    Mockito.verify(list, atMost(1)).clear();
    

    这行代码验证 list.clear() 方法最多被调用了一次。也就是说,调用0次或1次都是满足条件的,但超过1次则验证失败。

  4. atLeast(1)

    Mockito.verify(list, atLeast(1)).clear();
    

    这行代码表示 list.clear() 方法至少需要被调用一次。这和 atLeastOnce() 是等价的,都是要求目标方法至少执行一次。

通过这些方法,你可以更精确地控制并验证测试过程中对模拟对象方法的调用次数,从而确保代码按预期进行交互。


对Service层的单元测试实操案例

回到,我们这篇文章最初的一个问题,如果要对Service层中涉及数据库的方法调用该怎么做,我们直接上实操代码,如下:

相关代码
dto层Fans类

public class Fans implements Serializable {private static final long serialVersionUID = 1L;@TableId(value = "id", type = IdType.ASSIGN_UUID)private String uuid;private long uid;private long fid;public Fans(long uid, long fid) {this.uid = uid;this.fid = fid;}
}

Service层

@Service
public class FansServiceImpl extends ServiceImpl<FansMapper, Fans> implements IFansService {@Autowiredprivate FansMapper fansMapper;@Overridepublic boolean addFans(Fans fans) {String uuid = UUID.randomUUID().toString();fans.setUuid(uuid);return fansMapper.insert(fans) == 1 ;}
}

test类

class TestFans {// FansServiceImpl对象,用于测试@InjectMocksprivate FansServiceImpl fansService;// FansMapper对象,用于模拟@Mockprivate FansMapper fansMapper;@Testpublic void testAddFans() {// 创建Fans对象Fans fans = new Fans(6,62L);// 模拟FansMapper的insert方法,返回1when(fansMapper.insert(any(Fans.class))).thenReturn(1);// 使用doReturn方法模拟FansMapper的insert方法,返回1// doReturn(1).when(fansMapper.insert(any(Fans.class)));// 调用fansService的addFans方法boolean result = fansService.addFans(fans);// 断言结果为trueAssertions.assertTrue(result);// 验证fansMapper的insert方法被调用了一次verify(fansMapper, times(1)).insert(any(Fans.class));}
}

在这里插入图片描述
在这段代码中,需要做出点解释,对于fansService来说,是我们要测试的对象,所以我们需要用@InjectMocks来进行注释,说明这个是我们的目标测试对象,也就是“打桩”,而fansMapper则使用@Mock来注释是因为,fansMapper是fansService对象的一个属性,@InjectMock会自己去找被@Mock所注释的并且合适的虚拟对象进行依赖注入(类似于@Component中有的对象属性使用了@Autowired


END
声明:本文章为个人学习笔记,希望能对你有帮助,后续也会慢慢更新有关Mockito相关的文章,感谢阅读!!!

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

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

相关文章

R3F(React Three Fiber)经验篇

之前一直在做ThreeJS方向&#xff0c;整理了两篇R3F&#xff08;React Three Fiber&#xff09;的文档&#xff0c;这是经验篇&#xff0c;如果您的业务场景需要使用R3F&#xff0c;可以参考一下这个文档。下面是目录&#xff0c;按照需求自取。 基础篇 ⬇️ R3F&#xff08;…

SPI总线结构和原理

一、概述 SPI&#xff08;Serial Peripheral Interface&#xff09;是一种同步串行通信接口标准&#xff0c;被广泛应用于各种微控制器和外设之间的通信。SPI总线结构简单、易于扩展&#xff0c;并且支持多主设备同时操作。 二、信号线 SCK&#xff08;Serial Clock&#xf…

SpringIOC之support模块StaticMessageSource

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

zemax冉斯登目镜

两个焦距相等的平凸透镜组成&#xff0c;两个凸面相对&#xff0c;两者间距等于焦距的2/3 球差、轴向色差、畸变都小于惠更斯目镜 但是垂轴色差较大 可以当作普通放大镜使用 这里没有可以控制两个平凸透镜焦距相等 入瞳直径4mm波长0.51、0.56、0.61半视场15焦距28.2mm 镜头…

Javase补充-Arrays类的常用方法汇总

文章目录 一 . 排序方法二 . 查找方法三 . 判断是否相等的方法四 . 拷贝方法五 . 填充方法 一 . 排序方法 我们第一个要介绍的就是sort方法 这个排序实现的底层逻辑应该是十分复杂的,以我们目前的水平体系应该无法理解,我们今天尝试用我们可以理解的一种排序算法,插入排序来模…

软考41-上午题-【数据库】-关系代数运算3-外连接

一、外连接 连接的拓展&#xff0c;处理由于连接运算而缺失的信息。 1-1、回顾自然连接 1-2、左外连接 示例&#xff1a; 左边的表&#xff0c;数值是全的 1-3、右外连接 示例&#xff1a; 右边的表&#xff0c;数值是全的 1-4、全外连接 示例&#xff1a; 自然连接左外连接…

Linux--shell编程中内部表和外部表之间的转换

内部表和外部表之间的转换 1、查询表的类型 desc formatted student; Table Type: MANAGED_TABLE 2、修改内部表student为外部表 alter table student set tblproperties(EXTERNALTRUE); 3、查询表的类型 desc formatted student; Table Type: EXT…

python:读 Freeplane.mm文件,使用 xml.etree 生成测试案例.csv文件

Freeplane 是一款基于 Java 开源软件&#xff0c;继承 Freemind 的思维导图工具软件&#xff0c;它扩展了知识管理功能&#xff0c;在 Freemind 上增加了一些额外的功能&#xff0c;比如数学公式、节点属性面板等。 强大的节点功能&#xff0c;不仅仅节点的种类很多&#xff0…

【Ubuntu】解决Ubuntu 22.04开机显示器颜色(高对比度/反色)异常的问题

使用Ubuntu 22.04时强制关机了一下&#xff08;make -j16把电脑搞崩了&#xff09;&#xff0c;开机后系统显示的颜色异常&#xff0c;类似高对比度或反色&#xff0c;如下图。看着很难受&#xff0c;字体也没办法辨认。还好之前遇到过类似的问题&#xff0c;应该是一个配置文件…

132 Linux 系统编程9 ,IO操作,lseek 函数,truncate函数,查看文件的表示形式

一 lseek 函数 函数说明&#xff1a;此函数用于文件偏移 Linux中可使用系统函数lseek来修改文件偏移量(读写位置) 每个打开的文件都记录着当前读写位置&#xff0c;打开文件时读写位置是0&#xff0c;表示文件开头&#xff0c;通常读写多少个字节就会将读写位置往后移多少个字…

进程 2月24日学习笔记

1.进程: 程序&#xff1a;存放在外存中的一段数据组成的文件 进程&#xff1a;是一个程序动态执行的过程,包括进程的创建、进程的调度、进程的消亡 2.进程相关命令: 1.top 动态查看当前系统中的所有进程信息&#xff08;根据CPU占用率排序&#xff09; PID:唯一识…

容器镜像详解

1. 镜像组成 一个标准的OCI容器镜像由index, manifest, config, image layers这几个部分组成。 以docker镜像为例&#xff0c;下载的镜像文件保存在/var/lib/docker/目录下面 image/overlay2子目录下面保存着镜像相关的一些元数据 在下面的介绍主要以nginx:latest镜像为例子…

Ps:原色通道直方图(CMYK)

在 CMYK 颜色模式下&#xff0c;Photoshop 的“通道”面板中有青色、洋红、黄色及黑色四个原色通道。 与 RGB 颜色模式基于光的加法混合不同&#xff0c;CMYK 颜色模式基于颜料的减法混合&#xff0c;更适合反映实际印刷中油墨的使用情况。 默认情况下&#xff0c;CMYK 原色通道…

STM32 TCP实现OTA

芯片&#xff1a;stm32f407 开发平台&#xff1a;stm32cubeide 上位机开发平台&#xff1a;visual studio 2017 1. FLASH分配 将flash划分为四个部分&#xff1a; bootloader: 0x8000000-0x800ffff app1: 0x8010000-0x805ffff app2: …

Linux配置Maven环境变量

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

【尚硅谷】MybatisPlus 学习笔记(上)

目录 一、MybatisPlus简介 1.1、简介 1.2、支持数据库 二、入门案例 2.1、开发环境 2.2、创建数据库及表 创建表 添加数据 2.3、创建SpringBoot项目 初始化工程 导入依赖 idea中安装lombok插件 配置application.yml 启动类 实体类 添加mapper 测试 添加日志 …

算法沉淀——动态规划之斐波那契数列模型(leetcode真题剖析)

算法沉淀——动态规划之斐波那契数列模型 01.第 N 个泰波那契数02.三步问题03.使用最小花费爬楼梯04.解码方法 动态规划&#xff08;Dynamic Programming&#xff0c;简称DP&#xff09;是一种通过将原问题分解为相互重叠的子问题并仅仅解决每个子问题一次&#xff0c;将其解存…

应用回归分析:贝叶斯回归

贝叶斯回归是一种统计方法&#xff0c;它利用贝叶斯定理来更新对回归参数的估计。这种方法不仅考虑了数据的不确定性&#xff0c;还考虑了模型参数的不确定性&#xff0c;为预测提供了一个更加全面的框架。在本文中&#xff0c;我们将深入探讨贝叶斯回归的基本概念、如何实现它…

项目分享,正在开发的一款多商户家政平台服务小程序【商户端】页面截图-技术栈为uniapp可生成H5/APP/小程序

页面展示 商户端首页 【工作台】 此部分的功能逻辑如下&#xff1a; 工作台&#xff1a;商户的操作中心 基本信息管理&#xff1a;商户端的工作台允许商户维护自己的基本信息&#xff0c;包括上传头像、设置店名、查看入驻时间以及选择店铺类型&#xff08;企业店铺或个人店…

http协议及httpd的使用

HTTP协议介绍 HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础设计HTTP最初的目的是为了提供一种远距离共享知识的方式&#xff0c;借助多文档进行关…