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
相关产品推荐
相关产品推荐

