添加spring-cloud-starter-sleuth后Spring Boot Rest Doc测试失败求助
解决Spring Boot WebFlux RestDocs测试加Sleuth后上下文提前关闭的问题
我之前也碰到过类似的棘手问题,结合你的场景来看,核心矛盾是批量测试时的Spring上下文复用冲突,再加上Sleuth对上下文生命周期的额外影响,导致部分测试在上下文被关闭后仍试图调用它。下面是具体的分析和可落地的解决方案:
问题根源拆解
- JUnit 5并行测试的干扰:Maven Surefire插件默认可能开启了并行执行(尤其是新版本),当多个测试类共享同一个Spring上下文时,Sleuth注入的链路追踪Bean会打乱生命周期——某一个测试结束后触发了上下文关闭,其他测试却还在复用这个已失效的上下文。
- 上下文缓存的Key冲突:Sleuth会修改上下文的Bean定义(比如添加TraceFilter等组件),导致不同测试类的上下文缓存Key被错误判定为相同,进而复用了已经被某个测试关闭的上下文。
- 不同OS的线程调度差异:Windows和Linux的线程调度策略不同,导致不同测试类先执行完毕并触发上下文关闭,这就是为什么不同环境下失败的测试类不一样。
分步解决方案
1. 先禁用测试并行执行(快速验证)
首先验证是不是并行执行导致的问题,在pom.xml中配置Maven Surefire插件关闭并行:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.2</version> <configuration> <parallel>none</parallel> <useUnlimitedThreads>false</useUnlimitedThreads> <!-- 确保对JUnit 5的支持 --> <providerSelection>junit-platform</providerSelection> </configuration> </plugin>
运行mvn test看看问题是否消失,如果解决了,说明并行执行是主要诱因。
2. 强制每个测试类使用独立上下文(保留并行的情况下)
如果需要保持测试并行执行,可以给每个RestDocs测试类添加@DirtiesContext注解,让Spring在每个测试类结束后销毁对应的上下文,避免复用冲突:
@ExtendWith({ RestDocumentationExtension.class, SpringExtension.class }) @AutoConfigureRestDocs("target/generated-snippets") @SpringBootTest(classes = ArchimedesApplication.class) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) // 添加这一行 class OptControllerDocTest { // ... 你的测试代码 }
这个注解会告诉Spring测试框架:当前测试类会"污染"上下文,测试结束后必须销毁它。这样每个测试类都拥有独立的上下文,不会互相干扰。
3. 调整Sleuth的自动配置(如果不需要测试链路追踪)
如果你的RestDocs测试不需要验证链路追踪功能,可以在测试中排除Sleuth的WebFlux相关自动配置,减少对上下文的干扰:
@SpringBootTest( classes = ArchimedesApplication.class, exclude = {SleuthWebFluxAutoConfiguration.class, TraceWebFilterAutoConfiguration.class} )
这样Sleuth就不会给WebFlux添加额外的过滤器和Bean,从源头避免上下文生命周期异常。
4. 升级依赖版本(彻底解决潜在Bug)
你使用的Spring Boot 2.1.4和Sleuth 2.1.1都是比较老旧的版本,存在一些已知的上下文生命周期管理Bug。建议升级到对应分支的最新稳定版:
- Spring Boot升级到
2.1.18.RELEASE(2.1.x分支的最后一个稳定版) - Spring Cloud Sleuth升级到
2.1.9.RELEASE(与Spring Boot 2.1.x兼容的最新版)
新版本修复了很多测试上下文相关的问题,可能从根源上解决这个冲突。
5. 临时Workaround:确保上下文活跃再构建WebTestClient
如果以上方法都暂时无法实施,可以在@BeforeEach中检查上下文是否活跃,必要时重新刷新:
@BeforeEach void beforeEach(RestDocumentationContextProvider restDocumentation) { ConfigurableApplicationContext configurableContext = (ConfigurableApplicationContext) context; if (!configurableContext.isActive()) { configurableContext.refresh(); } this.webTestClient = WebTestClient.bindToApplicationContext(context) .configureClient() .filter(documentationConfiguration(restDocumentation)) .build(); }
不过这只是临时的补救措施,还是建议从前面的方案中选择彻底解决的方式。
内容的提问来源于stack exchange,提问作者Florian E.
相关产品推荐
相关产品推荐

