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

相关文章

PostgreSQL的扩展dict_int应用案例解析

《PostgreSQL的扩展dict_int应用案例解析》dict_int扩展为PostgreSQL提供了专业的整数文本处理能力,特别适合需要精确处理数字内容的搜索场景,本文给大家介绍PostgreS... 目录PostgreSQL的扩展dict_int一、扩展概述二、核心功能三、安装与启用四、字典配置方法

Python中re模块结合正则表达式的实际应用案例

《Python中re模块结合正则表达式的实际应用案例》Python中的re模块是用于处理正则表达式的强大工具,正则表达式是一种用来匹配字符串的模式,它可以在文本中搜索和匹配特定的字符串模式,这篇文章主... 目录前言re模块常用函数一、查看文本中是否包含 A 或 B 字符串二、替换多个关键词为统一格式三、提

Python get()函数用法案例详解

《Pythonget()函数用法案例详解》在Python中,get()是字典(dict)类型的内置方法,用于安全地获取字典中指定键对应的值,它的核心作用是避免因访问不存在的键而引发KeyError错... 目录简介基本语法一、用法二、案例:安全访问未知键三、案例:配置参数默认值简介python是一种高级编

MySQL中的索引结构和分类实战案例详解

《MySQL中的索引结构和分类实战案例详解》本文详解MySQL索引结构与分类,涵盖B树、B+树、哈希及全文索引,分析其原理与优劣势,并结合实战案例探讨创建、管理及优化技巧,助力提升查询性能,感兴趣的朋... 目录一、索引概述1.1 索引的定义与作用1.2 索引的基本原理二、索引结构详解2.1 B树索引2.2

从入门到精通MySQL 数据库索引(实战案例)

《从入门到精通MySQL数据库索引(实战案例)》索引是数据库的目录,提升查询速度,主要类型包括BTree、Hash、全文、空间索引,需根据场景选择,建议用于高频查询、关联字段、排序等,避免重复率高或... 目录一、索引是什么?能干嘛?核心作用:二、索引的 4 种主要类型(附通俗例子)1. BTree 索引(

使用Python进行GRPC和Dubbo协议的高级测试

《使用Python进行GRPC和Dubbo协议的高级测试》GRPC(GoogleRemoteProcedureCall)是一种高性能、开源的远程过程调用(RPC)框架,Dubbo是一种高性能的分布式服... 目录01 GRPC测试安装gRPC编写.proto文件实现服务02 Dubbo测试1. 安装Dubb

HTML中meta标签的常见使用案例(示例详解)

《HTML中meta标签的常见使用案例(示例详解)》HTMLmeta标签用于提供文档元数据,涵盖字符编码、SEO优化、社交媒体集成、移动设备适配、浏览器控制及安全隐私设置,优化页面显示与搜索引擎索引... 目录html中meta标签的常见使用案例一、基础功能二、搜索引擎优化(seo)三、社交媒体集成四、移动

Python的端到端测试框架SeleniumBase使用解读

《Python的端到端测试框架SeleniumBase使用解读》:本文主要介绍Python的端到端测试框架SeleniumBase使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全... 目录SeleniumBase详细介绍及用法指南什么是 SeleniumBase?SeleniumBase

MybatisPlus service接口功能介绍

《MybatisPlusservice接口功能介绍》:本文主要介绍MybatisPlusservice接口功能介绍,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录Service接口基本用法进阶用法总结:Lambda方法Service接口基本用法MyBATisP

六个案例搞懂mysql间隙锁

《六个案例搞懂mysql间隙锁》MySQL中的间隙是指索引中两个索引键之间的空间,间隙锁用于防止范围查询期间的幻读,本文主要介绍了六个案例搞懂mysql间隙锁,具有一定的参考价值,感兴趣的可以了解一下... 目录概念解释间隙锁详解间隙锁触发条件间隙锁加锁规则案例演示案例一:唯一索引等值锁定存在的数据案例二: