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 {

    @Autowired
    private FansMapper fansMapper;

    @Override
    public boolean addFans(Fans fans) {
        String uuid = UUID.randomUUID().toString();
        fans.setUuid(uuid);
        return fansMapper.insert(fans) == 1 ;
    }
}

test类

class TestFans {
    // FansServiceImpl对象,用于测试
    @InjectMocks
    private FansServiceImpl fansService;
    // FansMapper对象,用于模拟
    @Mock
    private FansMapper fansMapper;

    @Test
    public void testAddFans() {
        // 创建Fans对象
        Fans fans = new Fans(6,62L);
        // 模拟FansMapper的insert方法,返回1
        when(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);
        // 断言结果为true
        Assertions.assertTrue(result);
        // 验证fansMapper的insert方法被调用了一次
        verify(fansMapper, times(1)).insert(any(Fans.class));
    }
}

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


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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

软件运维维保方案-套用文档

软件运维维保方案 项目情况 1.1 项目背景 简述项目的来源、目的和重要性。 说明项目的规模、预算和预期目标。 1.2 项目现状 分析当前系统/软件的运行状态、存在的问题和潜在风险。 提供最近一次的维护报告或相关统计数据。服务简述 2.1 服务内容 明确运维服务的具体内容&…

JSON(javaScript Object Notation,Js对象标记)—我耀学IT

Json是一种轻量级的数据交换格式&#xff0c;目前使用非常广泛&#xff0c;是一种轻量级的数据交换格式。易于人阅读和编写&#xff0c;可以在多种语言之间进行数据交换 。同时也易于机器解析和生成 1.1json的值: 值可以是对象、数组、数字、字符串或者三个字面值(false、nul…

【数据分析之Numpy基础003】数组形状大变身!轻松掌握改变数组形状的技巧

处理数组的一项重要工作就是改变数组的维度&#xff0c;包括提高数组的维度和降低数组的维度&#xff0c;还包括数组的转置、拼接、分隔等。 Numpy为大家提供了大量的API可以很轻松的完成这些数组的操作。 1、改变数组的维度 如上篇文章使用到的reshape方法&#xff0c;将一维…

各国的通胀是多少?央行又使用那些指标?昂首资本1分钟分享

各国的通胀是多少&#xff1f;央行又使用哪些指标&#xff1f;今天昂首资本1分钟快速分享 在美国和欧盟&#xff0c;作为一个中期通胀目标&#xff0c;使用了一个目标指标&#xff0c;通常是为长达两年的前景计算的。在美国和欧盟&#xff0c;中期通胀目标是2%。在俄罗斯&…

【NCom】:通过高温气相合成调节Pt-CeO2相互作用以提高晶格氧的还原性

摘要&#xff1a;在这项工作中&#xff0c;我们比较了通过两种方法制备的 Pt 单原子催化剂&#xff08;SAC&#xff09;的 CO 氧化性能&#xff1a;&#xff08;1&#xff09;传统的湿化学合成&#xff08;强静电吸附strong electrostatic adsorption–SEA&#xff09;&#xf…

Mybatis总结--传参

MyBatis 传递参数&#xff1a;从 java 代码中把参数传递到 mapper.xml 文件 六、一个简单参数&#xff1a; Dao 接口中方法的参数只有一个简单类型&#xff08; java 基本类型和 String &#xff09;&#xff0c; 占位符 #{ 任意字符 } &#xff0c;和方法的参数名无关…

电脑msvcp100.dll丢失了怎么办?msvcp100.dll丢失的5种解决方法

当计算机系统中无法找到msvcp100.dll文件&#xff0c;或者遭遇msvcp100.dll丢失的情况时&#xff0c;可能会引发一系列运行问题和功能障碍。msvcp100.dll是Microsoft Visual C Redistributable Package的一部分&#xff0c;这是一个至关重要的动态链接库文件&#xff0c;对于许…

如何利用EXCEL批量插入图片

目录 1.excel打开目标表格&#xff1b; 2.点开视图-宏-录制宏&#xff0c;可以改宏的名字或者选择默认&#xff1b; 3.然后点开视图-宏-查看宏 4.点编辑进去 5.修改代码&#xff1a; &#xff08;1&#xff09;打开之后会显示有一堆代码 &#xff08;2&#xff09;将这个…

【C++进阶】STL容器--list底层剖析(迭代器封装)

目录 前言 list的结构与框架 list迭代器 list的插入和删除 insert erase list析构函数和拷贝构造 析构函数 拷贝构造 赋值重载 迭代器拷贝构造、析构函数实现问题 const迭代器 思考 总结 前言 前边我们了解了list的一些使用及其注意事项&#xff0c;今天我们进一步深入…

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

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

PixPin:一键搞定截图、长截图、贴图、GIF

名人说&#xff1a;莫道桑榆晚&#xff0c;为霞尚满天。——刘禹锡&#xff08;刘梦得&#xff0c;诗豪&#xff09; 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、什么是PixPin&#xff1f;①PixPin②核心功…

C语言每日一题(61)盛最多水的容器

题目链接 力扣 11 盛最多水的容器 题目描述 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水…

flet 读取本地音频文件的信息,歌名,歌手,歌曲长度,封面

请先安装 pip install flet, tinytag 组件 tinytag 是用来读取音频文件的信息的 测试用最好找一个有封面的音频的文件, 我是windows电脑,打开预览模式,选中文件时候能够右边显示图片, 如下,我电脑上某个音频文件的封面 import flet as ft from tinytag import TinyTag import…

全方位了解CRM系统:功能、种类和费用详细解说

尽管CRM已经横空出世20余年&#xff0c;但在国内普及率依旧不足20%。很多企业管理者、业务负责人对CRM是什么&#xff1f;CRM的作用、类型和价格等概念一头雾水。希望通过这篇文章深入浅出的讲解让大家对CRM管理软件从陌生到熟知。 一、CRM是什么&#xff1f; 什么是CRM&…

nginx高级配置详解

目录 一、网页的状态页 1、状态页的基本配置 2、搭配验证模块使用 3、结合白名单使用 二、nginx 第三方模块 1、echo模块 1.1 编译安装echo模块 1.2 配置echo模块 三、nginx变量 1、内置变量 2、自定义变量 四、自定义图标 五、自定义访问日志 1、自定义日志格式…

Informer:高效长序列时间序列预测模型(更新中)

文章行文思路&#xff1a; 目录 一、背景&#xff1a;1.时间序列介绍&#xff1a;2.LSTF介绍&#xff1a;3.Transformer与Informer的关系&#xff1a; 二、Transformer&#xff1a;1.Transformer简介&#xff1a;2.Transformer整体架构&#xff1a;3.模型输入&#xff1a;3.1第…

SSRF靶场实战

SSRF&#xff08;curl&#xff09; SSRF&#xff08;file_get_content&#xff09;

源代码管理——码云Gitee

目录 Git安装 Gitee配置SSH 源代码管理常规操作 1.idea配置git 2.常规操作 Git安装 安装Git是进行源代码管理的基本步骤之一。以下是在本地安装Git的通用步骤&#xff0c;适用于Windows系统&#xff1a; 下载Git安装程序: 访问Git官网的下载页面&#xff1a;Git官网下载地…

day16_ListSet课后练习题 - 参考答案

文章目录 day16_课后练习题第1题第2题第3题第4题第5题第6题第7题第8题 day16_课后练习题 第1题 案例&#xff1a; ​ 1、用一个String[]数组存点数 ​ 2、用一个String[]数组存花色 ​ 3、用一个String[]数组存大王、小王 ​ 4、用上面的数组&#xff0c;生成一副扑克牌 …

keepalived+HAProxy+MySQL双主实验

keepalivedHAProxyMySQL双主实验 环境准备 node1(HAProxy1):192.168.184.10 node2(HAProxy2):192.168.184.20 node3(MySQL1):192.168.184.30 node4(MySQL2):192.168.184.40 虚拟IP vip&#xff1a;192.168.184.100MySQL部署 在node3执行以下脚本&#xff1a; #!/bin/bash sy…