当前位置: 首页 > news >正文

Springboot结合Mockito写单元测试实践和原理

文章目录

  • 前言
  • 一、使用
    • 最佳实践
    • 使用场景
    • @SpyBean失效场景
    • 解决Mock失效的问题
      • 避免FactoryBean的实现方式
      • 使用@MockBean,但是要指定name
    • 个人推荐
  • 二、原理
    • 1. @MockBean
    • 2.@SpyBean
    • 方法调用
  • 总结


前言

相信看我博客的都是javaer,工作中一般都是使用Springboot框架。
之前介绍过,可以利用@Transactional注解实现单测方法回滚,其实大家都知道Springboot-Test里面集成了Mockito,今天我们来介绍下怎么使用,以及原理是什么。


一、使用

最佳实践

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
public abstract class BaseTest {@SpyBeanprotected EcmsGateway ecmsGateway;// 注意这行,是数据库查询的mapper,需要配置name属性。@MockBean(name = "basicUserInfoPOExtMapper")protected BasicUserInfoPOExtMapper basicUserInfoPOExtMapper;}@Before@SneakyThrows //这个是lombok的注解,还是挺好用的public void init() {
Mockito.doReturn(Collections.singletonList(userInfoPO)).when(basicUserInfoPOExtMapper).listBasicUserInfo();
Mockito.doReturn(Collections.singletonList(contractBaseInfo)).when(ecmsGateway).queryMainContractList(Mockito.anyList());}

**注意mybatis的mapper必须使用@MockBean注解,且必须配置name属性。**但是也因此导致该mapper作用域下的所有方法如果没有指定返回范式,就会返回空对象。

使用逻辑也很简单,就是通过MockBean和SpyBean对spring容器里注入的bean进行改造,然后在方法执行之前,通过Mockito的方法来指定当方法运行时,mock返回值。
需要注意的是
MockBean注解注入的属性,所有的方法调用,只要不指定返回范式和Mock对象,都是返回一个空的数据,null或者空的容器;而SpyBean注解注入的属性,默认方法调用还是走真实的链路调用,只有指定了返回范式的调用才会返回Mock的数据。

使用场景

一般情况下,咱们需要mock数据的场景是对于RPC调用,或者数据库查询(当然这个其实也是一种RPC)调用。
需要注意,很多同学发现Mock数据库查询会失效,具体原因和下面的是同一种情况!!!

@SpyBean失效场景

通过FactoryBean返回的对象类型是一个proxy时

Caused by: org.mockito.exceptions.base.MockitoException: 
Cannot mock/spy class com.sun.proxy.$Proxy146
注意这句报错!!!!!!
Mockito cannot mock/spy because :- final classat org.springframework.boot.test.mock.mockito.SpyDefinition.createSpy(SpyDefinition.java:103)at org.springframework.boot.test.mock.mockito.MockitoPostProcessor.createSpyIfNecessary(MockitoPostProcessor.java:358)at org.springframework.boot.test.mock.mockito.MockitoPostProcessor$SpyPostProcessor.createSpyIfNecessary(MockitoPostProcessor.java:496)at org.springframework.boot.test.mock.mockito.MockitoPostProcessor$SpyPostProcessor.postProcessAfterInitialization(MockitoPostProcessor.java:492)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:431)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.postProcessObjectFromFactoryBean(AbstractAutowireCapableBeanFactory.java:1836)at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:116)... 106 more

解决Mock失效的问题

避免FactoryBean的实现方式

目前mybatis针对poMapper的实现都是通过MapperFactoryBean实现的,所以,最简单的方式,可以基于mapper封装repository层,然后对repository层的对象添加@MockBean或者@SpyBean,进行调用。
譬如:

@Repository
public class BasicUserInfoRepository implements IBasicUserInfoRepository {@Resourceprivate BasicUserInfoPOExtMapper basicUserInfoPOExtMapper;
}@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SpringBootStudyApplication.class)
public class BaseTest {@SpyBeanprotected IBasicUserInfoRepository iBasicUserInfoRepository;}

使用@MockBean,但是要指定name

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SpringBootStudyApplication.class)
public class BaseTest {@MockBean(name = "basicUserInfoPOExtMapper")protected BasicUserInfoPOExtMapper basicUserInfoPOExtMapper;}

个人推荐

其实针对查询数据库的场景,个人还是推荐通过@Transactional注解达到数据回滚来实现查询数据的诉求,毕竟要是通过对poMapper添加@MockBean使得数据库查询能够Mock,但是由此导致了该poMapper的所有方法都需要指定返回范式。实例如下:

   @Transactional@Testpublic void testReceive() {// 前置将任务状态修改Long jobId = 255L;SePriceJob priceJob = new SePriceJob();priceJob.setId(jobId);priceJob.setAcceptType(JobAcceptType.CREATE_SUPPLIER_MAKE_UP.getAcceptType());priceJob.setStatus(PriceJobStatus.DONE.getStatus());priceJob.setTaskStatus(PriceJobTaskStatus.PROCESSING.getStatus());priceJobMapper.updateByPrimaryKeySelective(priceJob, SePriceJob.Column.acceptType, SePriceJob.Column.status, SePriceJob.Column.taskStatus);List<SePriceJob> priceJobList = priceJobMapper.selectByExample(SePriceJobExample.newAndCreateCriteria().andIdEqualTo(jobId).example());assert priceJobList.get(0).getTaskStatus().equals(PriceJobTaskStatus.PROCESSING.getStatus());}

二、原理

1. @MockBean

@MockBean注解的实现原理
需要注意的是MockBean有时候需要指定name属性,否则默认注入到Spring容器中的对象beanName是类的全限定名,导致其他bean在注入的时候获取到的不是该mockBean。

2.@SpyBean

@SpyBean注解对于原本bean是通过FactoryBean添加到容器,且被proxy过的实例,是没法实现Mock的。
@SpyBean注解的实现原理
可以看到不管是@MockBean还是@SpyBean,都是给spring的bean创建增加一个MockMethodInterceptor。
区别在于

1. @MockBean创建的mock对象是直接new出来的,而且只有MockMethodInterceptor这一个advisor;而@SpyBean是通过给spring容器创建的对象增加一个advisor:MockMethodInterceptor
2. 那MockMethodInterceptor在执行的时候是怎么区分当前对象到底是@MockBean还是@SpyBean修饰呢?MockMethodInterceptor对象有个属性MockCreationSettings mockCreationSettings,它有个属性defaultAnswer。@MockBean指定了该属性为Answers.RETURNS_DEFAULTS,而@SpyBean通过MockitoPostProcessor的createSpyIfNecessary,给指定的取值是Mockito.CALLS_REAL_METHODS

方法调用

方法调用的源码就不贴了,简单来说就是给MockMethodInterceptor实例增加一个返回范式,譬如创建一个matcher,指定该matcher命中时,返回某个对象;实际运行时,通过匹配matcher来决定是否返回Mock结果。


总结

  1. 文章主要讲了Springboot中的@MockBean和@SpyBean的使用场景和简单原理。
  2. 大家使用数据库一般都是通过spring-mybatis将mapper注入到spring容器的,导致使用@SpyBean不会生效,推荐使用@MockBean,同时指定name属性来实现数据库查询的返回Mock。当然笔者更推荐大家使用@Transactional注解实现回滚来达到数据库查询结果的设置。

相关文章:

Springboot结合Mockito写单元测试实践和原理

文章目录 前言一、使用最佳实践使用场景SpyBean失效场景解决Mock失效的问题避免FactoryBean的实现方式使用MockBean&#xff0c;但是要指定name 个人推荐 二、原理1. MockBean2.SpyBean方法调用 总结 前言 相信看我博客的都是javaer&#xff0c;工作中一般都是使用Springboot框…...

操作系统之微内核架构

宏内核相反&#xff0c;微内核架构提倡功能尽可能少&#xff0c;只提供进程调度、处理中断、内存映射、进程间通信等功能。微内核架构是不能够提供什么实际功能的&#xff0c;而内存管理、进程管理、设备管理和文件管理服务等&#xff0c;都被做成一个个服务进程&#xff0c;它…...

24---WPF缓存

一、什么是缓存&#xff1a; 1.缓存指的是将需要频繁访问的网络内容存放在离用户较近、访问速度更快的系统中&#xff0c;以提高内容访问速度的一种技术。缓存服务器就是存放频繁访问内容的服务器。 2.缓存就是一个临时存放区域--离用户比较近 二、作用--意义---如果系统出现故…...

vite+vue3.0 使用tailwindcss

参考资料&#xff1a; 安装 - TailwindCSS中文文档 | TailwindCSS中文网 npm install -D tailwindcss npm install postcss npm install autoprefixer npx tailwindcss init -p 生成/src/tailwind.config.js和/src/postcss.config.js配置文件 在/src/tailwind.config.js配置文件…...

C++QT---QT-day3

使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;密码是否为&…...

小程序如何搭建在服务器上

小程序可以通过搭建在服务器上&#xff0c;来实现跨平台的访问和使用。以下是搭建小程序在服务器上的步骤&#xff1a; 安装Node.js&#xff1a;首先&#xff0c;你需要在服务器上安装Node.js。你可以从Node.js的官方网站下载并安装。 安装微信开发者工具&#xff1a;然后&…...

JavaEE初阶学习:Servlet

1.Servlet 是什么 Servlet 是一种 Java 程序&#xff0c;用于在 Web 服务器上处理客户端请求和响应。Servlet 可以接收来自客户端&#xff08;浏览器、移动应用等&#xff09;的 HTTP 请求&#xff0c;并生成 HTML 页面或其他格式的数据&#xff0c;然后将响应发送回客户端。S…...

黑白二维码不好看,那么快学习改色的方法吧

现在经常会看到很多的二维码不是黑白图案&#xff0c;可以是其他纯色或者渐变色等样式的&#xff0c;那么怎么将黑白二维码改成其他鲜艳好看的颜色呢&#xff1f;一般想要修改普通样式的二维码可以用二维码美化生成器来处理&#xff0c;只需要上传二维码图片&#xff0c;就可以…...

coreldraw2024版本有哪些新增功能?

有小伙伴在用电脑查找软件程序的时候&#xff0c;看到了一款叫cdr软件的应用&#xff0c;自己之前没接触过&#xff0c;不知道cdr是什么软件&#xff1f;cdr软件是干什么的&#xff1f;十分好奇。其实它是一款平面设计软件&#xff0c;下面就给大家介绍下相关的cdr软件的知识。…...

2023最新Office2021专业增强版安装使用教程

Microsoft Office专业增强版2021是一套办公软件套装&#xff0c;包含了Word、Excel、PowerPoint、Outlook、Access、Publisher、OneNote、Teams等应用程序。这个版本是在Office 365的基础上推出的新版本&#xff0c;与之前的Office版本相比&#xff0c;增强了许多功能。也是目前…...

实时配送跟踪功能的实现:外卖跑腿小程序的技术挑战

在当今数字化时代&#xff0c;外卖和跑腿服务已经成为了生活中不可或缺的一部分。为了提供更好的用户体验&#xff0c;外卖跑腿小程序越来越注重实时配送跟踪功能的实现。这项技术挑战旨在确保顾客可以方便地跟踪他们的订单&#xff0c;以及配送员可以高效地完成送货任务。本文…...

react实现一维表格、键值对数据表格key value表格

UI画的需求很抽象&#xff0c;直接把数据铺开&#xff0c;不能直接用antd组件了 上一行是name&#xff0c;下一行是value&#xff0c;总数不定&#xff0c;最后前端还要显示求和 class OneDimensionTable extends React.Component { render() {const { data } this.props;le…...

个人微信CRM客户管理系统怎么选?功能介绍

现在市面上有许多种类的个人微信CRM客户管理系统可供选择&#xff0c;因此&#xff0c;我们需要选择最适合自己需求的微信管理系统CRM&#xff0c;最重要的是根据您的需求和期望的功能来进行筛选。 如何选择适合自己的微信CRM客户管理系统&#xff1f; 现在市面上的系统五花八…...

Mac Intellij Idea get/set方法快捷键

Control Retrun(回车键) Command n 参考&#xff1a; Mac Intellij Idea get/set方法快捷键-CSDN博客...

并发程序设计

一、进程的创建和回收 一、进程的概念 1、进程&#xff01;程序 程序是静态的&#xff0c;而进程是动态的 2、进程和程序的区别 1&#xff09;进程控制块中包含进程的属性 2&#xff09;程序在磁盘里面&#xff0c;堆栈都是在内存中&#xff0c;程序运行起来都在内存中 3…...

openGauss学习笔记-104 openGauss 数据库管理-管理数据库安全-客户端接入之SSL证书管理-证书替换

文章目录 openGauss学习笔记-104 openGauss 数据库管理-管理数据库安全-客户端接入之SSL证书管理-证书替换104.1 操作场景104.2 前提条件104.3 注意事项104.4 操作步骤 openGauss学习笔记-104 openGauss 数据库管理-管理数据库安全-客户端接入之SSL证书管理-证书替换 openGaus…...

react仿照antd progress实现可自定义颜色的直角矩形进度条

可传颜色、带滑块的直角进度条 很歹毒的UI设计&#xff08;真的很丑&#xff09; 实现&#xff1a; class RankProgress extends React.Component {render() {const { percent, progressColor } this.props;return (<div className{styles.progress}><div classNam…...

【网络安全】被恶意攻击的IP地址有多可怕?

被恶意攻击的IP地址可以导致一系列问题&#xff0c;其严重性和可怕程度取决于攻击的性质、目标、攻击者的动机以及受影响的系统或组织。以下是一些可能出现的问题和可怕性的因素&#xff1a; 数据泄露和盗窃&#xff1a;攻击者可能试图入侵系统&#xff0c;窃取敏感数据&#x…...

Guava-RateLimiter详解

简介&#xff1a; 常用的限流算法有漏桶算法和令牌桶算法&#xff0c;guava的RateLimiter使用的是令牌桶算法&#xff0c;也就是以固定的频率向桶中放入令牌&#xff0c;例如一秒钟10枚令牌&#xff0c;实际业务在每次响应请求之前都从桶中获取令牌&#xff0c;只有取到令牌的请…...

【C++11】右值引用、移动构造、移动赋值、完美转发 的原理介绍

文章目录 一、概念1.1 左值1.2 左值引用1.3 什么是右值&#xff1f;1.4 什么是右值引用&#xff1f;对于参数左值还是右值的不同&#xff0c;是被重载支持的左值引用的使用场景 和 缺陷 二、移动语义2.1 移动拷贝构造2.2 移动赋值 三、右值引用 与 STL3.1 移动拷贝构造 和 赋值…...

C++ STL 容器扩容与内存分配机制

C STL容器作为现代C编程的核心组件&#xff0c;其高效的动态内存管理机制一直是开发者关注的焦点。当vector需要容纳更多元素时&#xff0c;它是如何优雅地扩展容量的&#xff1f;unordered_map又是如何平衡哈希冲突与内存消耗的&#xff1f;本文将深入剖析STL容器扩容与内存分…...

抗体研发核心工具测评:酵母 / 噬菌体文库与展示技术

一、技术定位&#xff1a;生物治疗抗体研发的基石工具单克隆抗体&#xff08;mAbs&#xff09;及其衍生物是生物治疗领域的核心支柱&#xff0c;尤其在肿瘤、自身免疫病等疾病治疗中占据不可替代的地位。抗体研发的起始阶段 —— 抗原特异性抗体筛选&#xff0c;直接决定治疗性…...

微信好友数据分析与班级学生信息分析实战

微信好友数据分析与班级学生信息分析一、设计思想两个数据分析案例&#xff0c;旨在综合运用Python数据分析与可视化库&#xff08;Pandas、Matplotlib、PyEcharts、WordCloud、SnowNLP等&#xff09;&#xff0c;完成从数据读取、清洗、分析到可视化的全流程。设计思想如下&am…...

2026年服装收银软件选型指南:五大功能决定门店提效与增长

很多服装门店都遇到过这样的困境&#xff1a;网络波动导致无法收款&#xff0c;眼睁睁看着顾客放下衣服离开&#xff1b;促销规则设置不到位&#xff0c;收银时算错优惠引发客诉&#xff1b;活动结束了&#xff0c;线上线下数据对不上&#xff0c;投入的钱看不到效果。这些问题…...

OpenClaw技能开发入门:为Qwen3-14b_int4_awq定制PDF解析模块

OpenClaw技能开发入门&#xff1a;为Qwen3-14b_int4_awq定制PDF解析模块 1. 为什么需要自定义PDF解析技能 去年我在处理一批技术白皮书时&#xff0c;发现OpenClaw内置的文件处理能力对复杂PDF支持有限。当我想让AI助手自动提取PDF中的表格数据并生成摘要时&#xff0c;系统总…...

comsol实能带建模、与Matlab能带数据后处理 文献复现---“周期嵌套声学黑洞结构的复...

comsol实能带建模、与Matlab能带数据后处理 文献复现---“周期嵌套声学黑洞结构的复能带和凋落波研究”-“二维声学黑洞声子晶体的宽频振动抑制”-“ Broadband vibration mitigation using a two-dimensional acoustic black hole phononic crystal” 包括comsol实能带模型、M…...

ThinkJS路由系统终极指南:构建RESTful API的10个最佳实践

ThinkJS路由系统终极指南&#xff1a;构建RESTful API的10个最佳实践 【免费下载链接】thinkjs Use full ES2015 features to develop Node.js applications, Support TypeScript. 项目地址: https://gitcode.com/gh_mirrors/thi/thinkjs ThinkJS路由系统是构建现代Node…...

C++编程中new与delete操作符的深度解析

C编程中new与delete操作符的深度解析 在C编程的广阔天地里&#xff0c;内存管理是一个既基础又至关重要的环节。对于每一位C开发者而言&#xff0c;掌握内存的动态分配与释放是构建高效、稳定应用程序的基石。在众多内存管理工具中&#xff0c;new与delete操作符无疑是最为核心…...

OpenClaw技能市场探秘:Qwen3-32B-Chat镜像赋能10大自动化场景

OpenClaw技能市场探秘&#xff1a;Qwen3-32B-Chat镜像赋能10大自动化场景 1. 为什么需要技能市场&#xff1f; 第一次接触OpenClaw时&#xff0c;我误以为它只是个"高级版按键精灵"。直到在ClawHub技能市场看到wechat-publisher这个模块——它能直接将Markdown文章…...

海南自由贸易港借助“.CN”域名塑造线上专属品牌形象

自海南自由贸易港全岛封关运作以来&#xff0c;市场主体加速集聚&#xff0c;数字化转型需求持续释放&#xff0c;“.CN”域名逐步融入自贸港园区与入驻企业的线上品牌构建场景&#xff0c;成为其彰显数字化身份的重要标识。作为政策落地与产业集聚的核心平台&#xff0c;海南自…...