You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot @SpringBootTest多模块测试属性加载不一致问题

Spring Boot多模块测试@SpringBootTest属性加载异常问题

背景

我有一个拆分多个子模块的Spring Boot应用,每个子模块中的测试类均使用@SpringBootTest注解,且都继承自同样带有该注解的父类。

父类简化代码

@SpringBootTest(classes = {
    IntegrationTestsApp.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {
            "security.basic.enabled = false" })
@ExtendWith(SpringExtension.class)
@TestPropertySource(locations = "classpath:application-test.properties")

public class BaseIntegrationTest {

// ...

}

子模块测试示例代码

@SpringBootTest(classes = {
    IntegrationTestsApp.class }, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = {
    "app.server.address = 127.0.0.1" })
public class ServiceATest extends BaseIntegrationTest {

@Autowired
private ServiceA service;


@Test
void test() {
    System.out.println(getClass().getName()+" AppContextService.getContext().getEnvironment().getProperty(\"app.server.address\") = " 
    + AppContextService.getContext().getEnvironment().getProperty("app.server.address"));
    System.out.println("PropsResolver.resolve(${app.server.address}) = " + new PropsResolver().resolve("${app.server.address}"));
    System.out.println("PropsResolver.resolve(${app.server.address:localhost}) = " + new PropsResolver().resolve("${app.server.address:localhost}"));  
    // assertions

}
}

问题现象

单独运行测试、子模块内运行测试时,app.server.address取值为注解中定义的127.0.0.1;但全包(包含多个子模块)运行测试时,部分测试取到默认值localhost,且同子模块内的不同测试表现也不一致。

补充信息

  • 同子模块的TestA和TestB均使用相同内容的@SpringBootTest注解,且继承同一父类;
  • 若将app.server.address配置到application-test.properties中,所有测试场景下均能取到127.0.0.1。

初步排查

经讨论推测可能存在@SpringBootTest注解冲突问题,但进一步排查发现创建了多个ApplicationContext,且上下文会跨子模块复用,且冲突测试数量少于上下文数量。

疑问

  1. 若Spring为每个子模块创建上下文,注解属性值的差异来源是什么?
  2. 同包内为何部分测试能加载注解属性,部分只能取默认值?
  3. 是否确实存在注解冲突?该如何进一步排查?

问题解答

1. 注解属性值差异的来源

Spring测试上下文的复用核心是上下文缓存键,这个键由@SpringBootTest的所有属性(包括父类注解属性)、测试类配置类、类加载器、环境参数等共同决定。

  • 父类与子类的@SpringBootTest注解属性是合并关系,properties数组会拼接,但如果后续复用的上下文是由父类或其他不带app.server.address属性的测试类创建的,那么该上下文的环境中就没有这个属性,测试类自然会取默认值。
  • 跨子模块的类加载器差异、IntegrationTestsApp类在不同子模块中的加载实例不同,也会导致缓存键不一致,进而创建不同上下文,属性自然存在差异。

2. 同包内测试表现不一致的原因

核心是上下文复用的随机性:
测试运行顺序(无论串行还是并行)是不确定的,如果某测试先运行并创建了包含app.server.address=127.0.0.1的上下文,后续同包测试复用该上下文就能取到正确值;但如果先运行的测试创建的是不带该属性的上下文(比如仅基于父类注解创建的上下文),后续同包测试复用这个上下文就会取到默认值。

3. 是否存在注解冲突及排查方法

不存在所谓的“注解冲突”,本质是Spring测试上下文缓存机制与属性加载顺序的协同问题,排查方向如下:

  • 开启上下文缓存日志:在测试日志配置中添加logging.level.org.springframework.test.context.cache=DEBUG,可查看每个测试类使用的缓存键、上下文是新建还是复用,直接定位错误复用的场景。
  • 检查缓存键构成:通过Debug模式查看DefaultCacheKey的内容,确认不同测试类的缓存键差异点(比如类加载器、注解属性、配置类是否一致)。
  • 临时禁用缓存排查:在测试类上添加@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD),强制每个测试方法创建新上下文,若问题消失则确认是缓存复用导致。
  • 统一配置方式:避免父类与子类同时使用@SpringBootTest,可将公共配置移到application-test.properties(你已验证此方法有效),或用@TestConfiguration统一配置逻辑。

内容的提问来源于stack exchange,提问作者SkogensKonung

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 05:03:26