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);
}
问题分析
-
@TestPropertySource
的差异- 测试类 1 中未设置
cloud.id.space.prefix
,使用默认值。 - 测试类 2 中设置了
cloud.id.space.prefix=CC
。 - 由于
cloud.id.space.prefix
的值不同,provideCloudIdGenerator
方法生成的OrderIdGenerator
实例也不同。
- 测试类 1 中未设置
-
独立的
ApplicationContext
- Spring 为每个测试类创建了独立的
ApplicationContext
,导致cloud-id-generator
Bean 被初始化两次,分别使用了不同的属性值。
- Spring 为每个测试类创建了独立的
-
数据不一致
- 测试类 1 和测试类 2 中的
OrderIdGenerator
实例不同,可能导致测试结果不一致。
- 测试类 1 和测试类 2 中的