SpringBoot 测试实践 - 3:@MockBean、@SpyBean 、提升测试运行速度、Testcontainer
编写测试的时候,我们必须保证外部依赖行为一致,也需要模拟一些边界条件,所以我们需要使用 Mock 来模拟对象的行为。SpringBoot 提供了 @MockBean
和 @SpyBean
注解,可以方便地将模拟对象与 Spring 测试相结合,简化测试代码的编写
@MockBean
@MockBean
是 Spring Boot Test提供的注解,用于在 Spring Boot 测试中创建一个模拟的 Bean 实例,并注入到测试类中的依赖项中。使用 Mock 可以控制被 Mock 对象的行为:自定义返回值、抛出指定异常等,模拟各种可能的情况,提高测试的覆盖率
@SpringBootTest
@RunWith(SpringRunner.class)
public class MyServiceTest {
@MockBean
private ExternalDependency externalDependency;
@Autowired
private MyService myService;
@Test
public void testSomeMethod() {
// 定义外部依赖的行为
Mockito.when(externalDependency.someMethod()).thenReturn("Mocked Result");
// 调用被测试类的方法
// 被测方法内部调用了 ExternalDependency 的 someMethod 方法
String result = myService.someMethod();
// 验证外部依赖的方法是否被调用
Mockito.verify(externalDependency).someMethod();
// 断言结果
assertEquals("Mocked Result", result);
}
}
需要注意的是:使用了 @MockBean
,会创建完全模拟的对象,它完全替代了被模拟的 Bean,并且所有方法的调用都被模拟。对于未指定行为的方法,返回值如果是基本类型则返回对应基本类型的默认值,如果是引用类型则返回 null
@SpyBean
@SpyBean
是 Spring Boot Test 提供的另一个注解,与 @MockBean
作用相似,但是它创建的是部分模拟对象,未指定方法行为时,将执行被模拟对象的真实实现,返回实际方法的执行结果
常见的情况是:测试依赖外部资源(例如数据库、文件系统等)的方法,我们要在测试中模拟其中一部分方法的行为,同时保留对外部资源的实际访问,那么可以使用 @SpyBean
@SpringBootTest
public class MyServiceTest {
@Autowired
private MyService myService;
@SpyBean
private MyRepository myRepository;
@Test
public void testMyService() {
// 使用 doReturn 方法模拟调用 myRepository 的方法,并返回指定的值
Mockito.doReturn(new MyEntity()).when(myRepository).findById(1L);
MyResult result = myService.doSomething(1L);
Assertions.assertEquals("success", result.getMessage());
}
}
这里有一个很重要的点是:@SpyBean
使用 doReturn
而不是 thenReturn
,因为 Spy 对象是基于实例创建的,而 thenReturn 方法会调用实例方法并返回模拟结果,这可能会导致实例状态发生变化,从而影响后续的测试步骤。换而言之如果 Spy 对象使用 doReturn
就像这样:Mockito.when(myRepository.findById(1L)).thenReturn(new MyEntity());
这段代码我们本意是指定这个 Spy 对象的 findById(1L)
的行为,但是实际上 when 语句中 myRepository.findById(1L)
已经执行了了实际的逻辑,这可能影响整个测试
简化 Spring Context 提升测试运行速度
SpringBoot 测试实践 - 2:单元测试与集成测试 中提到了单元测试用到的 @MybatisTest
以及集成测试用到的 @SpringBootTest
。@SpringBootTest
加载整个 Spring Boot 应用程序的上下文,就像启动了整个 SpringBoot 应用,而 @MybatisTest
只配置了用于测试 MyBatis 的组件,速度就非常快。完整的项目有大量的测试用例,如果每个测试都重新加载 Spring Context 这样就非常耗时,所以要尽量减少 @SpringBootTest
一般业务代码都会注入外部依赖,如果只在测试方法上使用 @Test
注解,这样运行测试就会抛出空指针异常,需要在类上使用 @ExtendWith(SpringExtension.class)
将 Spring 的测试支持集成到 JUnit 5 中,这样就可以在测试类中获得 Spring 容器的支持,以便进行依赖注入、加载配置文件、使用 Spring Bean 等。仅加上这个注解是不够的,Spring 容器内依然没有我们需要的依赖,我们还需要使用 @ContextConfiguration()
指定要加载的配置文件、配置类或其他资源
如果说 @SpringBootTest
是初始化好所有项目中用到的 Bean 的话,那 @ExtendWith(SpringExtension.class)
就是按需取用,所以必须保证被测的类用到的所有依赖对象都装配进 Spring 的 IoC 容器里,否则就会抛出这样的异常:
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'com.test.ConfigurationRepository#0':
Unsatisfied dependency expressed through field 'configurationRepository';
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'com.test.ConfigurationRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations:
{@org.springframework.beans.factory.annotation.Autowired(required=true)}
我们可以通过 @ContextConfiguration()
来指定配置类或者是类 class。对于被测方法没有使用某些依赖也可以直接用 @MockBean
配置一个Mock 对象,保证测试能正常运行
@ExtendWith(SpringExtension.class)
// 指定加载的两个类:RestTemplate.class 和 ExecutorConfig.class
// ExecutorConfig.class 一个自定义的配置类,包含线程池配置
@ContextConfiguration(classes = {RestTemplate.class, ExecutorConfig.class})
class UserServiceTest {
// 注入被测对象
@Autowired
private UserService userService;
// 使用 Mock 代替容器加载依赖
@MockBean
private ConfigurationRepository configurationRepository;
// 通过 @ContextConfiguration,确保 Spring Context 中会包含 RestTemplate 的相关配置
@Autowired
private RestTemplate restTemplate;
}
避免 ApplicationContext 复用
默认情况下,运行测试 ApplicationContext
会被复用,以加快测试的运行速度。但是在某些情况下,比如:多个测试类继承同一个抽象类,这可能会导致测试运行失败。可以在抽象类或每个子类中使用 @DirtiesContext,让 Spring 在测试这些类后重置 ApplicationContext
@DirtiesContext
默认的 classMode
参数为ClassMode.AFTER_CLASS
该模式会在 整个测试类运行完毕后重新加载 Spring 测试上下文。如果希望每次测试方法运行后都重新加载 ApplicationContext
可以使用 @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
@DirtiesContext 也可以用于方法级别,在方法运行前或运行后标记为需要重新加载 ApplicationContext
@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class CacheIntegrationTest {
@Autowired
private CacheService cacheService;
@Test
@Order(1)
@DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD)
public void testCacheEviction() {
// 模拟缓存数据,缓存实际为 HashMap
cacheService.addToCache("key1", "value1");
cacheService.addToCache("key2", "value2");
}
@Test
@Order(2)
public void testCacheLookup() {
// 从缓存中查找数据
// 因为使用了 @DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD) ApplicationContext 重置,故缓存为空
String value1 = cacheService.getFromCache("key1");
String value2 = cacheService.getFromCache("key2");
}
}
Testcontainer
为了不影响测试环境的数据,涉及数据层修改的测试,我们可以使用 H2 数据库或者是像上一节:SpringBoot 测试实践 - 2:单元测试与集成测试 这样的,直接使用专门的测试数据库。还有一种方式是用 Docker 启动一个全新的数据库供测试环境使用。而 Testcontainer 的目标就是简化了整个流程:通过代码的方式指定镜像,测试一启动 Testcontainer
将完成初始化工作,自动拉取镜像并创建容器,测试结束后将关闭对应的容器
一些 CI 广泛地使用 TestContainer
保证测试环境的一致性。但是如果本地运行,Testcontainer
依赖本地的 Docker Daemon 或是 Testcontainers Cloud 这样的方案。Windows 本地部署 Docker 也会更麻烦一些
TestContainer
需要与容器运行时进行交互,第一次要拉取镜像,所以速度上相对慢一些,但是得益于 Docker,TestContainer
几乎可以启动任何服务,无论是数据库、缓存或者是 MQ 等等的,可以保证外部环境的一致性
教程可以参考官方的实践:
Testcontainer SpringBootTest 案例
上一节:SpringBoot 测试实践 - 2:单元测试与集成测试
参考资料
Context Management and Caching
Pitfalls on Testing with Spring Boot
A Quick Guide to @DirtiesContext
Modern Best Practices for Testing in Java
Testing :: Spring Framework
Protect REST APIs with Spring Security Reactive and JWT
Spring - Testing
SpringBoot 测试实践 - 3:@MockBean、@SpyBean 、提升测试运行速度、Testcontainer的更多相关文章
- 携程酒店DevOps测试实践
作者简介 王幸福,携程酒店研发部高级测试经理,负责无线自动化测试相关工作.在测试框架和平台研发.移动测试.DevOps等领域有着丰富的经验. 如今很多大型互联网公司.创新型企业都在积极地进行DevOp ...
- Docker与自动化测试及其测试实践
Docker 与自动化测试 对于重复枯燥的手动测试任务,可以考虑将其进行自动化改造.自动化的成本在于自动化程序的编写和维护,而收益在于节省了手动执行用例的时间.简而言之,如果收益大于成本,测试任务就有 ...
- 9-MySQL DBA笔记-测试实践
第9章 测试实践 在第8章中介绍了测试所需要的理论知识,本章将为读者讲述实际的测试过程.实际测试一般包括硬件测试.MySQL基准测试及应用服务压力测试,下面将分别讲述这三方面的内容.此外,测试工具的选 ...
- 转 Jmeter测试实践:文件下载接口
Jmeter测试实践:文件下载接口 一 Jmeter步骤 1.打开jmeter4.0,新建测试计划,添加线程组.根据实际情况配置线程属性. 2.添加HTTP请求.根据接口文档进行配置. Basic ...
- 车载以太网第二弹 | 测试之实锤-IOP测试实践
前言 上一期"物理层PMA测试实践",咱们从环境设备组成.被测对象组成再到测试过程和测试结果,将完整的PMA测试过程做了一个经验分享. 由下层开始逐层"披沙沥金" ...
- 《高级软件测试》web测试实践--12月30日记录
考完数学,我们正式开始web测试实践的作业,今天,我们主要进行了方案的选择和人员的分工.任务计划和安排如上图所示. 任务进展:完成题目选择和人员分工: 遇到问题:暂无: 下一步任务:完成软件评测.用户 ...
- springboot测试、打包、部署
本文使用<springboot集成mybatis(一)>项目,依次介绍springboot测试.打包.部署. 大多数朋友是做后端的,也就是为其他系统或者前端UI提供Rest API服务. ...
- SpringBootTest单元测试实战、SpringBoot测试进阶高级篇之MockMvc讲解
1.@SpringBootTest单元测试实战 简介:讲解SpringBoot的单元测试 1.引入相关依赖 <!--springboot程序测试依赖,如果是自动创建项目默认添加--> &l ...
- Redis3.2.5 集群搭建以及Spring-boot测试
1:集群中的机器信息 IP PORT 192.168.3.10 7000,7001,7002 192.168.3.11 7004,7005,7006 2:安装Redis 分别在10与11机器上面安装R ...
- 实践作业4:Web测试实践(小组作业)每日任务记录5
(一)今日任务更新 本次小组作业均已完成! 本组文件最终pdf文件(文件稍大,请耐心等待加载):https://files.cnblogs.com/files/ruanshuo170204/Web测试 ...
随机推荐
- 2021-06-22:现有司机N*2人,调度中心会将所有司机平分给A、B两个区域,第 i 个司机去A可得收入为income[i][0],第 i 个司机去B可得收入为income[i][1],返回所有调
2021-06-22:现有司机N*2人,调度中心会将所有司机平分给A.B两个区域,第 i 个司机去A可得收入为income[i][0],第 i 个司机去B可得收入为income[i][1],返回所有调 ...
- query查询原生sql
print(str(Teahcer.objects.filter(fans__gte=500).order_by('name').query)
- Vue选日期滚动条自动定位到选定的日期位置
html 这里的关键点就是 :id="'scroll'+index" 以及 :scroll-into-view="intoIndex" <view c ...
- 从前后端的角度分析options预检请求
摘要:options预检请求是干嘛的?options请求一定会在post请求之前发送吗?前端或者后端开发需要手动干预这个预检请求吗?不用文档定义堆砌名词,从前后端角度单独分析,大白话带你了解! 本文分 ...
- 2014年蓝桥杯C/C++大学B组省赛真题(奇怪的分式)
题目描述: 上小学的时候,小明经常自己发明新算法.一次,老师出的题目是:1/4 乘以 8/5 小明居然把分子拼接在一起,分母拼接在一起,答案是:18/45 (参见图1.png)老师刚想批评他,转念一想 ...
- Java 泛型:理解和应用
概述 泛型是一种将类型参数化的动态机制,使用得到的话,可以从以下的方面提升的你的程序: 安全性:使用泛型可以使代码更加安全可靠,因为泛型提供了编译时的类型检查,使得编译器能够在编译阶段捕捉到类型错误. ...
- 【原创】浅谈EtherCAT主站EOE(上)-EOE网络
这篇文章的标题虽然是关于EtherCAT EOE,但其实主要内容是关于整个EOE网络结构,属于计算机网络原理.而EtherCAT EoE只是简单介绍,并不是文章的重点.需要注意的是,我们的描述主要基于 ...
- StampedLock:高并发场景下一种比读写锁更快的锁
摘要:在读多写少的环境中,有没有一种比ReadWriteLock更快的锁呢?有,那就是JDK1.8中新增的StampedLock! 本文分享自华为云社区<[高并发]高并发场景下一种比读写锁更快的 ...
- 【2023 · CANN训练营第一季】昇腾AI入门Pytorch
昇腾AI全栈架构 华为AI全栈全场景解决方案为4层,分别为芯片层.芯片使能层.AI框架层和应用使能层. 芯片 基于统一.可扩展架构的系列化AI IP和芯片,为上层加速提供硬件基础. 芯片产品:昇腾31 ...
- Kubernetes(k8s)包管理工具Helm:Helm包管理
目录 一.系统环境 二.前言 三.包管理工具Helm简介 四.安装部署helm 五.配置helm以及helm常用命令 六.使用helm安装应用 七.搭建helm私有仓库 八.总结 一.系统环境 本文主 ...