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

2024-02-25 13:36

本文主要是介绍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相关的文章,感谢阅读!!!

这篇关于Mockito单元测试Mockito对Service层的测试案例的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/745636

相关文章

Nginx使用Keepalived部署web集群(高可用高性能负载均衡)实战案例

《Nginx使用Keepalived部署web集群(高可用高性能负载均衡)实战案例》本文介绍Nginx+Keepalived实现Web集群高可用负载均衡的部署与测试,涵盖架构设计、环境配置、健康检查、... 目录前言一、架构设计二、环境准备三、案例部署配置 前端 Keepalived配置 前端 Nginx

MySQL 复合查询案例详解

《MySQL复合查询案例详解》:本文主要介绍MySQL复合查询案例详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录基本查询回顾多表笛卡尔积子查询与where子查询多行子查询多列子查询子查询与from总结合并查询(不太重要)union基本查询回顾查询

Java Stream流使用案例深入详解

《JavaStream流使用案例深入详解》:本文主要介绍JavaStream流使用案例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录前言1. Lambda1.1 语法1.2 没参数只有一条语句或者多条语句1.3 一个参数只有一条语句或者多

MySQL 中的 JSON 查询案例详解

《MySQL中的JSON查询案例详解》:本文主要介绍MySQL的JSON查询的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql 的 jsON 路径格式基本结构路径组件详解特殊语法元素实际示例简单路径复杂路径简写操作符注意MySQL 的 J

Python Transformers库(NLP处理库)案例代码讲解

《PythonTransformers库(NLP处理库)案例代码讲解》本文介绍transformers库的全面讲解,包含基础知识、高级用法、案例代码及学习路径,内容经过组织,适合不同阶段的学习者,对... 目录一、基础知识1. Transformers 库简介2. 安装与环境配置3. 快速上手示例二、核心模

Python中使用正则表达式精准匹配IP地址的案例

《Python中使用正则表达式精准匹配IP地址的案例》Python的正则表达式(re模块)是完成这个任务的利器,但你知道怎么写才能准确匹配各种合法的IP地址吗,今天我们就来详细探讨这个问题,感兴趣的朋... 目录为什么需要IP正则表达式?IP地址的基本结构基础正则表达式写法精确匹配0-255的数字验证IP地

MySQL高级查询之JOIN、子查询、窗口函数实际案例

《MySQL高级查询之JOIN、子查询、窗口函数实际案例》:本文主要介绍MySQL高级查询之JOIN、子查询、窗口函数实际案例的相关资料,JOIN用于多表关联查询,子查询用于数据筛选和过滤,窗口函... 目录前言1. JOIN(连接查询)1.1 内连接(INNER JOIN)1.2 左连接(LEFT JOI

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

MySQL中实现多表查询的操作方法(配sql+实操图+案例巩固 通俗易懂版)

《MySQL中实现多表查询的操作方法(配sql+实操图+案例巩固通俗易懂版)》本文主要讲解了MySQL中的多表查询,包括子查询、笛卡尔积、自连接、多表查询的实现方法以及多列子查询等,通过实际例子和操... 目录复合查询1. 回顾查询基本操作group by 分组having1. 显示部门号为10的部门名,员

MyBatis-Plus中Service接口的lambdaUpdate用法及实例分析

《MyBatis-Plus中Service接口的lambdaUpdate用法及实例分析》本文将详细讲解MyBatis-Plus中的lambdaUpdate用法,并提供丰富的案例来帮助读者更好地理解和应... 目录深入探索MyBATis-Plus中Service接口的lambdaUpdate用法及示例案例背景