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,且上下文会跨子模块复用,且冲突测试数量少于上下文数量。
疑问
- 若Spring为每个子模块创建上下文,注解属性值的差异来源是什么?
- 同包内为何部分测试能加载注解属性,部分只能取默认值?
- 是否确实存在注解冲突?该如何进一步排查?
问题解答
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
相关产品推荐
相关产品推荐

