Spring 单元测试中如何进行 mock

2023-11-16

我们在使用 Spring 开发项目时,都会用到依赖注入。如果程序依赖了外部系统或者不可控组件,比如依赖数据库、网络通信、文件系统等,我们在编写单元测试时,并不需要实际对外部系统进行操作,这时就要将被测试代码与外部系统进行解耦,而这种解耦方法就叫作 “mock”。所谓 “mock” 就是用一个“假”的服务代替真正的服务。

那我们如何来 mock 服务进行单元测试呢?mock 的方式主要有两种:手动 mock 和利用单元测试框架 mock。其中,利用框架 mock 主要是为了简化代码编写。我们这里主要是介绍利用框架 mock,而手动 mock 只是简单介绍。

手动 mock

手动 mock 其实就是重新创建一个类继承被 mock 的服务类,并重写里面的方法。在单元测试中,利用依赖注入的方式使用 mock 的服务类替换原来的服务类。具体代码示列如下:

/**
 * UserRepository
 *
 * @author star
 */
@Repository
public class UserRepository {

    /**
     * 模拟从数据库中获取用户信息,实际开发中需要连接真实的数据库
     */
    public User getUser(String name) {
        User user = new User();
        user.setName("testing");
        user.setEmail("testing@outlook.com");

        return user;
    }
}

/**
 * MockUserRepository
 *
 * @author star
 */
public class MockUserRepository extends UserRepository {

    /**
     * 模拟从数据库中获取用户信息
     */
    @Override
    public User getUser(String name) {
        User user = new User();
        user.setName("mock-test-name");
        user.setEmail("mock-test-email");

        return user;
    }
}

// 进行单元测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceManualTest {

    @Autowired
    private UserService userService;

    @Test
    public void testGetUser_Manual() {
        // 将 MockUserRepository 注入到 UserService 中
        userService.setUserRepository(new MockUserRepository());
        User user = userService.getUser("mock-test-name");
        Assert.assertEquals("mock-test-name", user.getName());
        Assert.assertEquals("mock-test-email", user.getEmail());
    }
}

从上面的代码中,我们可以看到手动 mock 需要编写大量的额外代码,同时被测试类也需要提供依赖注入的入口(setter 方法等)。如果被 mock 的类修改了函数名称或者功能,mock 类也要跟着修改,增加了维护成本。

为了提高效率,减少维护成本,我们推荐使用单元测是框架进行 mock。

利用框架 mock

这里我们主要介绍 Mokito.mock()、@Mock、@MockBean 这三种方式的 mock。

Mocito.mock()

Mocito.mock() 方法允许我们创建类或接口的 mock 对象。然后,我们可以使用 mock 对象指定其方法的返回值,并验证其方法是否被调用。代码示列如下:

@Test
public void testGetUser_MockMethod() {
    // 模拟 UserRepository,测试时不直接操作数据库
    UserRepository mockUserRepository = Mockito.mock(UserRepository.class);
    // 将 mockUserRepository 注入到 UserService 类中
    userService.setUserRepository(mockUserRepository);

    User mockUser = mockUser();
    Mockito.when(mockUserRepository.getUser(mockUser.getName()))
            .thenReturn(mockUser);

    User user = userService.getUser(mockUser.getName());
    Assert.assertEquals(mockUser.getName(), user.getName());
    Assert.assertEquals(mockUser.getEmail(), user.getEmail());

    // 验证 mockUserRepository.getUser() 方法是否执行
    Mockito.verify(mockUserRepository).getUser(mockUser.getName());
}

@Mock

@Mock 是 Mockito.mock() 方法的简写。同样,我们应该只在测试类中使用它。与 Mockito.mock() 方法不同的是,我们需要在测试期间启用 Mockito 注解才能使用 @Mock 注解。

我们可以调用 MockitoAnnotations.initMocks(this) 静态方法来启用 Mockito 注解。为了避免测试之间的副作用,建议在每次测试执行之前先进行以下操作:

@Before
public void setup() {
    // 启用 Mockito 注解
    MockitoAnnotations.initMocks(this);
}

我们还可以使用另一种方法来启用 Mockito 注解。通过在 @RunWith() 指定 MockitoJUnitRunner 来运行测试:

@RunWith(MockitoJUnitRunner.class)
public class UserServiceMockTest { 

}

下面我们来看看如何使用 @Mock 进行服务 mock。代码示列如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceMockTest {

    @Mock
    private UserRepository userRepository;

    @Autowired
    @InjectMocks
    private UserService userService;

    private User mockUser() {
        User user = new User();
        user.setName("mock-test-name");
        user.setEmail("mock-test-email");

        return user;
    }

    @Before
    public void setup() {
        // 启用 Mockito 注解
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testGetUser_MockAnnotation() {
        User mockUser = mockUser();
        Mockito.when(userRepository.getUser(mockUser.getName()))
                .thenReturn(mockUser);

        User user = userService.getUser(mockUser.getName());
        Assert.assertEquals(mockUser.getName(), user.getName());
        Assert.assertEquals(mockUser.getEmail(), user.getEmail());

        // 验证 mockUserRepository.getUser() 方法是否执行
        Mockito.verify(userRepository).getUser(mockUser.getName());
    }

}

Mockito 的 @InjectMocks 注解作用是将 @Mock 所修饰的 mock 对象注入到指定类中替换原有的对象。

@MockBean

@MockBean 是 Spring Boot 中的注解。我们可以使用 @MockBean 将 mock 对象添加到 Spring 应用程序上下文中。该 mock 对象将替换应用程序上下文中任何现有的相同类型的 bean。如果应用程序上下文中没有相同类型的 bean,它将使用 mock 的对象作为 bean 添加到上下文中。

@MockBean 在需要 mock 特定 bean(例如外部服务)的集成测试中很有用。

要使用 @MockBean 注解,我们必须在 @RunWith() 中指定 SpringRunner 来运行测试。代码示列如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceMockBeanTest {

    @MockBean
    private UserRepository userRepository;

    private User mockUser() {
        User user = new User();
        user.setName("mock-test-name");
        user.setEmail("mock-test-email");

        return user;
    }

    @Test
    public void testGetUser_MockBean() {
        User mockUser = mockUser();
        // 模拟 UserRepository
        Mockito.when(userRepository.getUser(mockUser.getName()))
                .thenReturn(mockUser);
        // 验证结果
        User user = userRepository.getUser(mockUser.getName());
        Assert.assertEquals(mockUser.getName(), user.getName());
        Assert.assertEquals(mockUser.getEmail(), user.getEmail());

        Mockito.verify(userRepository).getUser(mockUser.getName());
    }
}

这里需要注意的是,Spring test 默认会重用 bean。如果 A 测试使用 mock 对象进行测试,而 B 测试使用原有的相同类型对象进行测试,B 测试在 A 测试之后运行,那么 B 测试拿到的对象是 mock 的对象。一般这种情况是不期望的,所以需要用 @DirtiesContext 修饰上面的测试避免这个问题。

最后,小伙伴们可以在 GitHub 中获取源码

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring 单元测试中如何进行 mock 的相关文章

  • List接口不是很详细的介绍

    文章目录 前言 一 List是什么 1 1 List概述 1 2 常用API 带有Index 都是List新增方法 1 3 List用法 二 常见实用类 2 1 ArrayList与Vector 2 2 ArrayList与LinkedLi
  • 微信小程序之behaviors

    目录 简介 使用方法 意义 简介 微信小程序的behaviors是一种可复用的代码块 可以在多个组件中共享 它类似于面向对象编程中的 继承 可以将一些通用的逻辑和方法封装在behaviors中 然后在需要使用的组件中引用该behaviors
  • SQL注入原理-报错盲注

    小伙伴们大家好 本期为大家带来的内容是SQL注入原理之报错盲注 目录 为什么要使用报错盲注 常见的报错函数 updatexml 函数 extractvalue 函数 实战演示 1 检测是否存在注入点 2 执行报错语句爆出数据 1 爆出当前数
  • Docker安装+基本操作+配置阿里云镜像仓库,以及Docker下mysql,tomcat,redis安装 包括redis.conf文件

    1 帮助启动类命令 2 镜像命令 3 容器命令 4 配置阿里云镜像仓库 1 登陆阿里云镜像仓库 2 往阿里云镜像仓库推送本地镜像 有教程 最后只用改一个版本号即可 版本最好不要重复 3 从阿里云镜像仓库拉取镜像 同上 非常简单 5 dock
  • 回调函数的作用

    回调函数的作用 原文地址 http wmnmtm blog 163 com blog static 3824571420105484116877 一直不太理解回调函数的作用 下面是找到的一些关于回调函数的作用的解答 1 回调函数是一个很有用
  • java正则表达式验证密码_java密码验证正则表达式校验

    正则表达式就是记录文本规则的代码 php密码验证正则表达式 8位长度限制 密码验证 password zongzi Abc oo13a2 n preg match all a zA Z d 8 password array 长度是8或更多
  • Spring中事件监听器

    Spring中事件监听器 概述 基本构成 Spring事件监听器应用 Spring中监听器流程和源码解析 概述 事件监听器是观察者模式的一个应用 当被观察的事件发生改变时需要通知该事件的订阅者针对这个事件做出对应行为 它将事件的发布和订阅进
  • MyBatisPlus之条件查询(常规查询、范围查询、模糊查询、null值处理等)

    MyBatisPlus之条件查询 MyBatisPlus之条件查询 1 设置查询条件 1 1 常规格式 1 2 链式编程格式 1 3 lambda格式1 1 4 lambda格式2 2 组合查询条件 2 1 并且 2 2 或者 3 条件查询
  • 项目难管理?先学会用好甘特图(内附操作方法及实用模板)

    先分享一些项目管理甘特图的模板 省事儿 高效 简单 再放制作教程 注 模板文中自取 部分Excel模板做成文件放在文末了 01 项目管理Excel套表 02 工程项目流程甘特图 03 项目管理甘特图表 模板指路 gt gt 工程项目管理模板
  • create-react-app中使用axios请求本地json文件

    在create react app创建react应用时 模拟本地请求静态json文件 必须把静态文件放到public下才可以请求到
  • appium+jenkins实例构建

    自动化测试平台 Jenkins简介 是一个开源软件项目 是基于java开发的一种持续集成工具 用于监控持续重复的工作 旨在提供一个开放易用的软件平台 使软件的持续集成变成可能 前面我们已经开完测试脚本 也使用bat 批处理来封装了启动App
  • P3369 【模板】普通平衡树【splay】

    题目链接 一个学习splay的链接 挺不错的哟 初识splay的时间里 总是会在各种各样的地方反着各种各样稀奇古怪的错误 好蒻 这次的错误是在pushup 的时候 我们更新其父节点的时候 不能直接使用 1 来做 而是要理解为什么是加上这个节
  • linux内核之无锁缓冲队列kfifo原理(结合项目实践)

    无锁缓冲队列kfifo 1 kfifo概述 2 kfifo功能描述 3 kfifo put与 kfifo get详解 4 kfifo get和kfifo put无锁并发操作 5 总结 6 项目使用介绍 7 其它 userspace 移植实现
  • Qt之QEvent

    简述 QEvent 类是所有事件类的基类 事件对象包含事件参数 Qt 的主事件循环 QCoreApplication exec 从事件队列中获取本地窗口系统事件 将它们转化为 QEvents 然后将转换后的事件发送给 QObjects 一般
  • 冠脉造影的医学背景知识

    1 冠脉造影读片技巧 医学界医生站
  • 内容:先有详细设计,还是先有接口文档?

    有朋友询问 对于一个项目来说是先写接口文档还是先设计表呢 有的人说 先写接口文档再慢慢完成表的设计 这样就可以避免由于太了解表了而导致对于返回的数据模型太受表的影响 针对这个问题 我们需要明确2个概念 并结合研发流程考虑信息依赖 才能更好地
  • avue dynamic动态子表单启用插槽slot功能的正确方式

    avue dynamic动态子表单启用插槽slot功能的正确方式 关于avue dynamic动态子表单 配置dynamic的children字段即可 内部组件为crud组件 大部分属性参考Crud文档 默认的type为curd 还可以配置

随机推荐

  • spring boot整合MySQL数据库

    spring boot整合MySQL数据库 spring boot整合MySQL数据库可以说很简单 只需要添加MySQL依赖和在配置文件中添加数据库配置信息 利用spring boot starter jdbc的JdbcTemplate即可
  • 集成算法——Adaboost代码

    集成算法是我们将不同的分类器组合起来 而这种组合结果就被称为集成方法或者是元算法 使用集成方法时会有多种形式 可以是不同算法的集成 也可以是同意算法在不同设置下的集成 还可以是数据集不同部分分配给不同分类器之后的集成 两种形式 baggin
  • 【总结】Markdown个人常用语法

    目录 输入 toc 按回车Enter Table of Contents TOC toc 标题 一级标题 二级标题 六级标题 段落 一个段落是由一个以上的连接的行句组成 一个以上的空行和Tab则会划分出不同的段落 一般的段落不需要用空白或换
  • GFPGAN源码分析—第十四篇

    项目总结 1 简述思想 本项目主要是利用预训练好的GAN生成器 StyleGAN 作为先验实现低质量人脸图片的修复 论文中提到 1 We leverage rich and diverse generative facial priors
  • vue中实现el-table点选和鼠标框选功能

    实现思路 项目有两个需求 既能在el table实现点选又能实现鼠标框选 一 点选实现思路 使用el table的cellClick方法 1 直接给点击的cell添加类名 cell classList add blue cell 然后把获取
  • golang 读取文件最后一行_测试用例是开发人员最后一块遮羞布

    最近一周写一个比较复杂的业务模块 越写到后面真心越心虚 操作越来越复杂了 代码也逐渐凌乱了起来 比如一个接口 传入的是一个比较复杂的大json 我需要解析这个大json 然后根据json中字段进行增删改查 调用第三方服务等操作 告诉前端接口
  • 微信支付配置流程

    微信支付配置流程 1 微信支付配置 微信公众号平台 gt 如果没有正式域名就要在安全中心配置白名单IP 2 微信公众号 微信公众号设置 gt 在功能设置里面的网页授权域名 3 商户号 产品中心 gt 开发配置 gt 配置jsApi支付页面的
  • vue3中a-table表格默认选中禁止选择

    效果 代码 使用row selection中的getCheckboxProps属性 其中preserveSelectedRowKeys属性设置为true 是为了表格切换页的时候 保留其他页面选中的数据 点击按钮 出现弹窗 const add
  • 深度学习:图像增强

    https blog csdn net zhangjunhit article details 79554140
  • Unity 使Text文本内容配合音频逐个显示并动态设置富文本

    在做一个项目时 需要Text文本内容逐个显示 并且配合音频的播放速度 当音频结束时 文本也显示完毕 而且给每一段文本设置不同的颜色和字号 代码如下 using System Collections using System Collecti
  • jenkins exec command 命令不执行

    本文记述的exec command 命令不执行情况是因为用户权限导致的 根据结果反馈就好像该设置不存在一样但是也没有报错 情况1 前端项目linux截图如下 图中1标志为jenkins所打的包 但是2标志是root账号的dist文件 因权限
  • plsql 登录后,提示数据库字符集(AL32UTF8)和客户端字符集(ZHS16GBK)不一致

    plsql 登录后提示 Database character set AL32UTF8 and Client character set ZHS16GBK are different Character set conversion may
  • Qt信号槽连接方式源码解读

    前言 Qt的五 四 种连接方式 在上一篇已经讲明 本篇主要分析在源码上是如何实现这几种连接方式的 本次源码为Qt 5 15 2 搞懂务必认真阅读最后添加注释后的代码 connect时会做什么 已知connect是可以实现一个信号连接多个槽的
  • ORT执行推理如何指定device编号

    法1 在执行推理前运行命令指定卡号 如 export CUDA VISIBLE DEVICES 1 python test py ox resnet50 16 fp32 说明 编号从0开始 通过上面的方式指定后 推理任务会在第2张卡中运行
  • window 服务器不稳定,服务器Windows系统突发情况的解决办法

    服务器Windows系统现在还是大部分的站长的选择 有很多用户是使用Windows作为网站服务器的系统 今天小编对于服务器Windows系统在维护过程中出现的几种突发情况 来讲讲解决办法 1 终端协议错误 如果用记事本或其他编辑器在远程终端
  • lambda 函数完美使用指南

    来源 萝卜大杂烩 今天我们来学习 Python 中的 lambda 函数 并探讨使用它的优点和局限性 什么是 Python 中的 Lambda 函数 lambda 函数是一个匿名函数 即 没有名称定义 它可以接受任意数量的参数 但与普通函数
  • 浅拷贝和深拷贝的区别

    浅拷贝 Shallow Copy 和深拷贝 Deep Copy 是两种复制对象的方式 它们之间的主要区别在于复制过程中是否会复制对象内部的引用类型数据 浅拷贝 Shallow Copy 浅拷贝仅复制了对象本身以及对象内部的基本数据类型 如i
  • 医学图像2D/3D可视化 ITK-SNAP软件使用

    软件下载链接 ITK SNAP Home 1 导入医学图像 nii gz文件 File gt Open Main Image 点击 Browse 切记不能有中文路径 gt Next 2 载入相应的分割图数据 nii gz文件 Segment
  • Android学习笔记——归纳整理

    目录 一 Android系统架构 二 Actvity相关 2 1基础相关 2 2 Intent相关 2 2 1 Intent的组成 2 2 2 显式Intent 2 2 3 隐式Intent 2 2 4 Intent属性 2 2 5 Int
  • Spring 单元测试中如何进行 mock

    我们在使用 Spring 开发项目时 都会用到依赖注入 如果程序依赖了外部系统或者不可控组件 比如依赖数据库 网络通信 文件系统等 我们在编写单元测试时 并不需要实际对外部系统进行操作 这时就要将被测试代码与外部系统进行解耦 而这种解耦方法