@TestPropertySource 造成 SpringBoot Test 中对同一个 Bean 使用不同实例

Spring Boot 的 Bean 默认确实是单例的,但在测试环境中,可能会因为以下原因导致两个测试类分别初始化了不同的 Bean:

1. 测试类的隔离性

Spring 的测试框架会为每个测试类创建一个独立的 ApplicationContext,以确保测试之间互不干扰。由于两个测试类的 @TestPropertySource 注解中定义了不同的属性(cloud.id.space.prefix 的值不同),这会导致 Spring 为每个测试类创建独立的上下文。

每个 ApplicationContext 都会重新加载配置并初始化所有的 Bean,因此即使是同一个 Bean 定义,也会在不同的上下文中被实例化多次。

2. @TestPropertySource 的作用

@TestPropertySource 会覆盖默认的配置属性。在你的两个测试类中,cloud.id.space.prefix 的值分别是默认值和 "CC",这会导致 provideCloudIdGenerator 方法中的 cloudIdSpacePrefix 参数值不同,从而生成两个不同的 OrderIdGenerator 实例。

3. Spring 测试上下文缓存

Spring 测试框架会尝试缓存 ApplicationContext,但缓存的前提是上下文的配置完全相同。如果两个测试类的配置(包括 @TestPropertySource)不同,Spring 会认为它们需要不同的上下文,因此不会复用缓存的上下文。

问题总结

在 Spring Boot 测试中,@TestPropertySource 注解会为每个测试类加载特定的属性配置。如果两个测试类的 @TestPropertySource 配置不同,Spring 会为每个测试类创建独立的 ApplicationContext,从而导致同一个 Bean 被初始化多次,并且每次初始化时使用的属性值可能不同。这种行为可能会导致测试数据不一致的问题。


代码说明

测试类 1

java 复制代码
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(classes = {ArchaiusAutoConfiguration.class, Application.class, TestApplication.class})
@ActiveProfiles("local")
@TestPropertySource(properties = {"management.server.port=0", "releaseVersion=local"})
public class TestClass1 {

    @Autowired
    @Qualifier("demo-id-generator")
    private DemoIdGenerator demoIdGenerator;

    @Test
    public void testDemoIdGenerator() {
        System.out.println(demoIdGenerator);
    }
}

测试类 2

java 复制代码
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(classes = {ArchaiusAutoConfiguration.class, Application.class, TestApplication.class})
@ActiveProfiles("local")
@TestPropertySource(properties = {"management.server.port=0", "releaseVersion=local", "cloud.id.space.prefix=CC"})
public class TestClass2 {

    @Autowired
    @Qualifier("demo-id-generator")
    private DemoIdGenerator demoIdGenerator;

    @Test
    public void testDemoIdGenerator() {
        System.out.println(demoIdGenerator);
    }
}

Bean 定义

java 复制代码
@Bean(value = "demo-id-generator", initMethod = "start", destroyMethod = "stop")
public DemoIdGenerator provideDemoIdGenerator(final DemoSpaceRepository demoSpaceRepository,
                                               @Value("${cloud.id.space.prefix}") final String cloudIdSpacePrefix,
                                               final WingtipsCompletable wingtipsCompletable) {
    final DemoSpace demoSpace = new DemoSpace(
            ORDER_ID,
            Optional.of(cloudIdSpacePrefix),
            Optional.empty(),
            Optional.of(12),
            Optional.of("0"),
            1L);
    return new DemoIdGenerator(demoSpaceRepository, demoSpace, wingtipsCompletable);
}

问题分析

  1. @TestPropertySource 的差异

    • 测试类 1 中未设置 cloud.id.space.prefix,使用默认值。
    • 测试类 2 中设置了 cloud.id.space.prefix=CC
    • 由于 cloud.id.space.prefix 的值不同,provideCloudIdGenerator 方法生成的 OrderIdGenerator 实例也不同。
  2. 独立的 ApplicationContext

    • Spring 为每个测试类创建了独立的 ApplicationContext,导致 cloud-id-generator Bean 被初始化两次,分别使用了不同的属性值。
  3. 数据不一致

    • 测试类 1 和测试类 2 中的 OrderIdGenerator 实例不同,可能导致测试结果不一致。
相关推荐
自不量力的A同学1 分钟前
Solon AI v3.9 正式发布:全能 Skill 爆发
java·网络·人工智能
万岳科技系统开发12 分钟前
食堂采购系统源码库存扣减算法与并发控制实现详解
java·前端·数据库·算法
独断万古他化16 分钟前
【Spring 原理】Bean 的作用域与生命周期
java·后端·spring
*小海豚*21 分钟前
在linux服务器上DNS正常,但是java应用调用第三方解析域名报错
java·linux·服务器
撩得Android一次心动37 分钟前
Android LiveData 全面解析:使用Java构建响应式UI【源码篇】
android·java·android jetpack·livedata
组合缺一41 分钟前
Solon AI (Java) v3.9 正式发布:全能 Skill 爆发,Agent 协作更专业!仍然支持 java8!
java·人工智能·ai·llm·agent·solon·mcp
MSTcheng.1 小时前
【C++】C++11新特性(二)
java·开发语言·c++·c++11
一 乐1 小时前
校园二手交易|基于springboot + vue校园二手交易系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端
KIKIiiiiiiii1 小时前
微信个人号API二次开发中的解决经验
java·人工智能·python·微信
80530单词突击赢1 小时前
SpringBoot整合SpringMVC全解析
java·spring boot·后端