SpringBootTest集成WireMock时多测试类运行出现连接拒绝问题
Spring Boot集成测试WireMock连接拒绝问题
我在Spring Boot集成测试中遇到了一个奇怪的问题,以下是初始化通用测试组件(WireMock和Spring WebTestClient)的抽象基类:
AbstractSpringIT.java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("integration-test") @AutoConfigureWebTestClient public abstract class AbstractSpringIT { @RegisterExtension static WireMockExtension wireMockServer = WireMockExtension.newInstance() .options(wireMockConfig().dynamicPort()) .build(); @DynamicPropertySource static void configureProperties(DynamicPropertyRegistry registry) { registry.add("app.backend-url", wireMockServer::baseUrl); } @Autowired WebTestClient webTestClient; }
有两个测试类继承了该抽象类:
AnotherIT.java
public class AnotherIT extends AbstractSpringIT { @Test void should_pass() { wireMockServer.stubFor(get("/some-path").willReturn(ok())); webTestClient.mutateWith(mockOAuth2Client("keycloak")) .get() .uri("/some-path") .exchange() .expectStatus() .is2xxSuccessful(); } }
SomeIT.java
public class SomeIT extends AbstractSpringIT { @Test void should_pass() { webTestClient.mutateWith(mockOAuth2Login()) .get() .uri("/api/user") .exchange() .expectStatus().isOk(); } }
这两个测试类单独运行时都能通过,但一起运行时,AnotherIT会抛出Connection refused: localhost/[0:0:0:0:0:0:0:1]:39813错误。经过排查发现:
- 按Maven默认的字母顺序运行(
AnotherIT先于SomeIT执行)时,两个测试类都能通过; - 当每个测试类单独定义自己的
WireMockExtension和@DynamicPropertySource时,所有测试都能正常通过。
问题根源
核心问题是静态WireMockExtension的生命周期与Spring测试上下文缓存的冲突:
- Java静态成员属于类本身,抽象基类中的
wireMockServer会被所有子类共享。第一个测试类执行完毕后,JUnit会自动停止该WireMock实例; - Spring测试上下文会复用已加载的上下文,导致
app.backend-url属性依然绑定到已停止的WireMock端口,后续测试请求自然连接失败; - 按字母顺序
AnotherIT先运行时,SomeIT不依赖WireMock,因此不会触发连接问题;反过来SomeIT先执行,WireMock被停止后AnotherIT再运行就会报错。
解决方案
方案1:让子类独立管理WireMock实例(推荐)
抽象基类只保留通用测试配置,让需要WireMock的子类自行定义扩展和属性绑定,避免共享静态实例:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("integration-test") @AutoConfigureWebTestClient public abstract class AbstractSpringIT { @Autowired WebTestClient webTestClient; // 提供工具方法统一属性绑定逻辑 protected static void registerWireMockProperties(DynamicPropertyRegistry registry, WireMockExtension wireMockServer) { registry.add("app.backend-url", wireMockServer::baseUrl); } }
AnotherIT(需要WireMock的子类):
public class AnotherIT extends AbstractSpringIT { @RegisterExtension static WireMockExtension wireMockServer = WireMockExtension.newInstance() .options(wireMockConfig().dynamicPort()) .build(); @DynamicPropertySource static void configureProperties(DynamicPropertyRegistry registry) { registerWireMockProperties(registry, wireMockServer); } @Test void should_pass() { wireMockServer.stubFor(get("/some-path").willReturn(ok())); webTestClient.mutateWith(mockOAuth2Client("keycloak")) .get() .uri("/some-path") .exchange() .expectStatus() .is2xxSuccessful(); } }
SomeIT(无需WireMock的子类)保持原代码即可。
方案2:禁用Spring上下文缓存
在抽象基类添加@DirtiesContext,强制每个测试类执行完毕后销毁上下文,重新创建时绑定新的WireMock端口。但此方式会显著增加测试耗时,仅作为临时解决方案:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("integration-test") @AutoConfigureWebTestClient @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public abstract class AbstractSpringIT { // 原有代码不变 }
方案3:使用非静态WireMockExtension实例
将基类中的wireMockServer改为非静态字段,每个测试实例都会创建独立的WireMock服务,同时配合@TestConstructor注入扩展实例到静态属性绑定方法(需JUnit 5.7+支持):
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("integration-test") @AutoConfigureWebTestClient @TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL) public abstract class AbstractSpringIT { @RegisterExtension WireMockExtension wireMockServer = WireMockExtension.newInstance() .options(wireMockConfig().dynamicPort()) .build(); @Autowired WebTestClient webTestClient; @DynamicPropertySource static void configureProperties(DynamicPropertyRegistry registry, WireMockExtension wireMockServer) { registry.add("app.backend-url", wireMockServer::baseUrl); } }
内容的提问来源于stack exchange,提问作者Robert Strauch
相关产品推荐
相关产品推荐

