SpringBoot 测试实践:单元测试与集成测试

简介: 在 Spring Boot 测试中,@MockBean 用于创建完全模拟的 Bean,替代真实对象行为;而 @SpyBean 则用于部分模拟,保留未指定方法的真实实现。两者结合 Mockito 可灵活控制依赖行为,提升测试覆盖率。合理使用 @ContextConfiguration 和避免滥用 @SpringBootTest 可优化测试上下文加载速度,提高测试效率。

编写测试的时候,我们必须保证外部依赖行为一致,也需要模拟一些边界条件,所以我们需要使用 Mock 来模拟对象的行为。SpringBoot 提供了 @MockBean@SpyBean 注解,可以方便地将模拟对象与 Spring 测试相结合,简化测试代码的编写

@MockBean

@MockBean 是 Spring Boot Test提供的注解,用于在 Spring Boot 测试中创建一个模拟的 Bean 实例,并注入到测试类中的依赖项中。使用 Mock 可以控制被 Mock 对象的行为:自定义返回值、抛出指定异常等,模拟各种可能的情况,提高测试的覆盖率

java

体验AI代码助手

代码解读

复制代码

@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

java

体验AI代码助手

代码解读

复制代码

@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 提升测试运行速度

@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

体验AI代码助手

代码解读

复制代码

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 对象,保证测试能正常运行

java

体验AI代码助手

代码解读

复制代码

@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

java

体验AI代码助手

代码解读

复制代码

@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 数据库,使用专门的测试数据库。还有一种方式是用 Docker 启动一个全新的数据库供测试环境使用。而 Testcontainer 的目标就是简化了整个流程:通过代码的方式指定镜像,测试一启动 Testcontainer 将完成初始化工作,自动拉取镜像并创建容器,测试结束后将关闭对应的容器

一些 CI 广泛地使用 TestContainer 保证测试环境的一致性。但是如果本地运行,Testcontainer依赖本地的 Docker Daemon 或是 Testcontainers Cloud 这样的方案。Windows 本地部署 Docker 也会更麻烦一些

TestContainer 需要与容器运行时进行交互,第一次要拉取镜像,所以速度上相对慢一些,但是得益于 Docker,TestContainer 几乎可以启动任何服务,无论是数据库、缓存或者是 MQ 等等的,可以保证外部环境的一致性


转载来源:https://juejin.cn/post/7274430147126214671

相关文章
|
13天前
|
人工智能 JSON 安全
Spring Boot实现无感刷新Token机制
本文深入解析在Spring Boot项目中实现JWT无感刷新Token的机制,涵盖双Token策略、Refresh Token安全性及具体示例代码,帮助开发者提升用户体验与系统安全性。
|
8天前
|
人工智能 Kubernetes 调度
基于 AI 网关和 llmaz,提升 vLLM 推理服务可用性和部署易用性的实践
本文介绍了如何使用 llmaz 快速部署基于 vLLM 的大语言模型推理服务,并结合 Higress AI 网关实现流量控制、可观测性、故障转移等能力,构建稳定、高可用的大模型服务平台。
144 16
|
8天前
|
人工智能 IDE 定位技术
通义灵码 AI IDE 上线,第一时间测评体验
通义灵码 AI IDE 重磅上线,开启智能编程新纪元!无需插件,开箱即用,依托通义千问大模型,实现高效、智能的编程体验。支持 MCP 工具链,可快速调用多种服务(如12306余票查询、高德地图标注等),大幅提升开发效率。结合 Qwen3 强大的 Agent 能力,开发者可通过自然语言快速构建功能,如智能选票系统、地图可视化页面等。行间代码预测、AI 规则定制、记忆能力等功能,让 AI 更懂你的编码习惯。Lingma IDE 不仅是工具,更是开发者身边的智能助手,助力 AI 编程落地实践。立即下载体验,感受未来编程的魅力!
123 17
|
12天前
|
人工智能 并行计算 持续交付
如何使用龙蜥衍生版KOS,2步实现大模型训练环境部署
大幅降低了用户开发和应用大模型的技术门槛。
|
8天前
|
人工智能 运维 Kubernetes
这家公司使用 MCP,已向企业交付 1000 名数字员工
君润人力是一家科技驱动的人力资源服务公司,专注于为服务业提供一站式人力资源解决方案。通过AI与数字员工技术,公司在招聘、社保等领域实现自动化服务,提升效率并降低成本。同时,君润积极探索MCP协议和Higress网关技术,构建“数字灵工”平台,推动人服行业的智能化转型。
|
6天前
|
SQL 人工智能 数据可视化
开源AI BI可视化工具-WrenAI
Wren AI 是一款开源的 SQL AI 代理,支持数据、产品及业务团队通过聊天、直观界面和与 Excel、Google Sheets 的集成获取洞察。它结合大型语言模型(LLM)与检索增强生成(RAG)技术,助力用户高效处理复杂数据分析任务。
|
11天前
|
人工智能 弹性计算 自然语言处理
从0到1部署大模型,计算巢模型市场让小白秒变专家
阿里云计算巢模型市场依托阿里云弹性计算资源,支持私有化部署,集成通义千问、通义万象、Stable Diffusion等领先AI模型,覆盖大语言模型、文生图、多模态、文生视频等场景。模型部署在用户云账号下,30分钟极速上线,保障数据安全与权限自主控制,适用于企业级私有部署及快速原型验证场景。
|
7天前
|
存储 人工智能 JavaScript
小米AI眼镜是值不值得买,看完就知道
2025年6月26日,小米正式发布首款AI眼镜,售价1999元起。搭载高通AR1芯片与恒玄2700,配备1200万摄像头、5麦克风阵列,支持录音转写、同声传译、卡路里识别等功能。可选电致变色镜片,双指轻划0.2秒变色。4GB+32GB存储组合,续航约50分钟。外观致敬Meta RayBan,经典百搭。虽定价略高,但功能丰富,适合有智能穿戴需求的用户。
|
15天前
|
人工智能 运维 安全
基于合合信息开源智能终端工具—Chaterm的实战指南【当运维遇上AI,一场效率革命正在发生】
在云计算和多平台运维日益复杂的今天,传统命令行工具正面临前所未有的挑战。工程师不仅要记忆成百上千条操作命令,还需在不同平台之间切换终端、脚本、权限和语法,操作效率与安全性常常难以兼顾。尤其在多云环境、远程办公、跨部门协作频繁的背景下,这些“低效、碎片化、易出错”的传统运维方式,已经严重阻碍了 IT 团队的创新能力和响应速度。 而就在这时,一款由合合信息推出的新型智能终端工具——Chaterm,正在悄然颠覆这一现状。它不仅是一款跨平台终端工具,更是业内率先引入 AI Agent 能力 的“会思考”的云资源管理助手。
60 6
|
13天前
|
人工智能 安全 Cloud Native
Nacos 3.0 架构全景解读,AI 时代服务注册中心的演进
Nacos 3.0 正式发布,定位升级为“一个易于构建 AI Agent 应用的动态服务发现、配置管理和 AI 智能体管理平台”。架构上强化了安全性,引入零信任机制,并支持 MCP 服务管理、AI Registry 等新特性,助力 AI 应用高效开发与运行。