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

Spring 5 WebClient测试咨询:替代WireMock的可行框架与技术

解决WebClient调用验证的替代方案

我之前在从RestTemplate迁移到WebClient时,也碰到过一模一样的问题——Mockito对这种流畅式链式调用的Mock简直是噩梦,WireMock偶尔的超时挂起更是让人头大。给你分享几个亲测有效的方案,你可以根据项目的测试场景来选:

1. Spring官方的MockWebServer(首推)

这是Spring团队专门为测试WebClient这类HTTP客户端打造的工具,完全融入Spring生态,稳定性比WireMock好很多,几乎不会出现莫名其妙的超时问题。

用法示例:

Spring Boot项目一般自带相关依赖,非Boot项目需手动引入spring-web-test-client,测试代码示例如下:

import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;

import static org.assertj.core.api.Assertions.assertThat;

class WebClientMockTest {
    private MockWebServer mockWebServer;
    private WebClient webClient;

    @BeforeEach
    void setup() {
        mockWebServer = new MockWebServer();
        // 将WebClient请求指向Mock服务器
        webClient = WebClient.builder()
                .baseUrl(mockWebServer.url("/").toString())
                .build();
    }

    @AfterEach
    void teardown() throws IOException {
        // 测试后关闭服务器,避免资源泄漏
        mockWebServer.shutdown();
    }

    @Test
    void verifyGetRequest() throws InterruptedException {
        // 预设响应内容
        mockWebServer.enqueue(new MockResponse()
                .setBody("{\"id\":1,\"name\":\"demo\"}")
                .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE));

        // 调用WebClient接口
        DemoResponse response = webClient.get()
                .uri("/api/demo")
                .retrieve()
                .bodyToMono(DemoResponse.class)
                .block();

        // 验证实际发送的请求细节
        RecordedRequest recordedRequest = mockWebServer.takeRequest();
        assertThat(recordedRequest.getMethod()).isEqualTo("GET");
        assertThat(recordedRequest.getPath()).isEqualTo("/api/demo");
        // 还可进一步验证请求头、请求体等内容
    }

    // 自定义响应实体类
    static class DemoResponse {
        private int id;
        private String name;

        // 构造器、getter/setter省略
        public DemoResponse(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }
}

优缺点:

  • ✅ 官方维护,稳定性拉满,和WebClient完美适配
  • ✅ 能精确验证请求的所有细节(方法、路径、headers、body)
  • ✅ 无需额外复杂配置
  • ⚠️ 需要在测试中调整WebClient的baseUrl指向Mock服务器,步骤简单易操作

2. Mockito链式Mock(适合轻量单元测试)

如果你的测试只是想验证WebClient的调用链(比如有没有调用get()、有没有传入正确的uri),不需要模拟真实HTTP交互,那可以用Mockito来Mock整个WebClient的链式调用。

用法示例:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class WebClientMockitoTest {
    @Mock
    private WebClient webClient;
    @Mock
    private WebClient.RequestHeadersUriSpec<?> requestHeadersUriSpec;
    @Mock
    private WebClient.RequestHeadersSpec<?> requestHeadersSpec;
    @Mock
    private WebClient.ResponseSpec responseSpec;

    private MyService myService;

    @BeforeEach
    void setup() {
        MockitoAnnotations.openMocks(this);
        myService = new MyService(webClient);

        // 链式Mock WebClient的调用流程
        when(webClient.get()).thenReturn(requestHeadersUriSpec);
        when(requestHeadersUriSpec.uri("/api/demo")).thenReturn(requestHeadersSpec);
        when(requestHeadersSpec.retrieve()).thenReturn(responseSpec);
        when(responseSpec.bodyToMono(DemoResponse.class)).thenReturn(Mono.just(new DemoResponse(1, "demo")));
    }

    @Test
    void verifyWebClientCall() {
        // 调用业务方法
        myService.fetchDemoData();

        // 验证WebClient的调用逻辑
        verify(webClient).get();
        verify(requestHeadersUriSpec).uri("/api/demo");
    }

    // 业务服务类示例
    static class MyService {
        private final WebClient webClient;

        public MyService(WebClient webClient) {
            this.webClient = webClient;
        }

        public Mono<DemoResponse> fetchDemoData() {
            return webClient.get()
                    .uri("/api/demo")
                    .retrieve()
                    .bodyToMono(DemoResponse.class);
        }
    }

    // 自定义响应实体类
    static class DemoResponse {
        private int id;
        private String name;

        public DemoResponse(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }
}

优缺点:

  • ✅ 无需启动任何HTTP服务器,测试速度极快
  • ✅ 适合纯单元测试场景,只关注调用逻辑
  • ⚠️ 链式Mock代码繁琐,WebClient调用链变化时需要同步修改测试
  • ⚠️ 无法验证真实HTTP请求的细节(比如实际发送的headers、body内容)

3. 优化WireMock配置(如果不想换工具)

如果你还是想继续用WireMock,之前的超时/挂起问题大概率是因为服务器资源没正确释放导致的。可以试试用Junit 5扩展来自动管理WireMock的生命周期:

用法示例:

import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;

import static com.github.tomakehurst.wiremock.client.WireMock.*;

@ExtendWith(WireMockExtension.class)
class WireMockFixedTest {
    @Test
    void testWithWireMock(WireMockServer wireMockServer) {
        // 配置Stub响应
        wireMockServer.stubFor(get(urlEqualTo("/api/demo"))
                .willReturn(aResponse()
                        .withStatus(200)
                        .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                        .withBody("{\"id\":1,\"name\":\"demo\"}")));

        // 配置WebClient指向WireMock
        WebClient webClient = WebClient.builder()
                .baseUrl(wireMockServer.baseUrl())
                .build();

        // 调用业务方法并验证逻辑...
        webClient.get()
                .uri("/api/demo")
                .retrieve()
                .bodyToMono(DemoResponse.class)
                .block();

        // 验证请求是否命中Stub
        wireMockServer.verify(getRequestedFor(urlEqualTo("/api/demo")));
    }

    // 自定义响应实体类
    static class DemoResponse {
        private int id;
        private String name;

        public DemoResponse(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }
}

优化点:

  • 使用WireMockExtension自动启动和关闭服务器,避免资源泄漏
  • 可在配置中调整超时参数,比如wireMockServer.setTimeout(5000)
  • 确保每个测试用例的Stub独立,避免交叉污染

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:49:56