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

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测试上下文缓存的冲突:

  1. Java静态成员属于类本身,抽象基类中的wireMockServer会被所有子类共享。第一个测试类执行完毕后,JUnit会自动停止该WireMock实例;
  2. Spring测试上下文会复用已加载的上下文,导致app.backend-url属性依然绑定到已停止的WireMock端口,后续测试请求自然连接失败;
  3. 按字母顺序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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 08:17:43