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

如何测试Spring Webflux中Disposable类型的WebClient调用?能否用stubFor?

Spring Webflux 中带 subscribe() 的 WebClient 调用测试方案

一、能不能用 stubFor?

完全可以用。stubFor 是 WireMock 的核心 API,专门用来模拟 HTTP 服务端响应,完美适配 WebClient 的 HTTP 调用场景——不管是同步还是异步调用都能覆盖。但要注意:代码里的 .subscribe() 会让请求在后台异步执行,测试时必须等待异步操作完成,不然测试会提前结束,根本验证不了请求是否真的发出去了。

二、具体操作步骤

1. 先准备依赖

确保测试依赖里包含 WireMock 和 Spring Webflux 测试包,比如 Maven 配置:

<!-- WireMock 用于模拟HTTP服务 -->
<dependency>
    <groupId>com.github.tomakehurst</groupId>
    <artifactId>wiremock-jre8</artifactId>
    <version>2.35.0</version>
    <scope>test</scope>
</dependency>
<!-- Spring Webflux 测试支持 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
    </exclusions>
</dependency>

2. 把 WebClient 指向 WireMock

测试时要让 WebClient 的请求地址指向 WireMock 的运行地址,示例代码如下:

@SpringBootTest
class TrelloCallbackTest {
    @Autowired
    private TrelloService trelloService; // 假设你的业务逻辑在这个类里

    private WireMockServer wireMockServer;

    @BeforeEach
    void setup() {
        // 启动WireMock服务,随机端口避免冲突
        wireMockServer = new WireMockServer(WireMockConfiguration.wireMockConfig().dynamicPort());
        wireMockServer.start();
        
        // 重新构建WebClient,把baseUrl设为WireMock的地址
        WebClient webClient = WebClient.builder()
                .baseUrl(wireMockServer.baseUrl())
                .build();
        // 把这个WebClient注入到业务类中(用setter或构造器都行)
        trelloService.setWebClient(webClient);
    }

    @AfterEach
    void teardown() {
        // 测试结束后停止WireMock
        wireMockServer.stop();
    }
}

3. 写 stubFor 并验证异步调用

因为是异步执行,需要给 WireMock 的验证逻辑加超时等待,确保异步请求有足够时间完成:

@Test
void trelloCallback_shouldSendValidPostRequest() {
    // 1. 定义Mock响应规则
    stubFor(post(urlEqualTo("/your-trello-callback-path")) // 替换成实际的URL路径
            .withHeader(HttpHeaders.CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON_VALUE))
            .withRequestBody(matchingJsonPath("$.yourField")) // 按需验证请求体内容
            .willReturn(aResponse().withStatus(200)));

    // 2. 执行测试方法
    Type testPayload = new Type(); // 构造测试用的请求体
    trelloService.trelloCallback(testPayload);

    // 3. 等待异步请求完成并验证
    verify(VerificationVerificationTimeout.timeout(5, TimeUnit.SECONDS),
            postRequestedFor(urlEqualTo("/your-trello-callback-path"))
                    .withHeader(HttpHeaders.CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON_VALUE)));
}

4. 额外提醒

原代码里调用 subscribe() 后直接返回 Mono.empty() 的写法不太合理——调用方完全感知不到异步请求的状态(成功/失败)。建议改成返回 Mono.defer(() -> webClient...).subscribeOn(Schedulers.boundedElastic()),这样测试时还能用到 StepVerifier 来订阅流并等待完成,更符合 Reactive 编程的规范。

三、不用 stubFor 的替代测试方式

如果不想用 WireMock,也可以用 Mockito 直接模拟 WebClient 的调用链,适合简单的单元测试场景(缺点是没法验证 HTTP 请求的实际格式):

@Test
void trelloCallback_shouldTriggerWebClientCall() {
    // 模拟WebClient的整个调用链
    WebClient webClient = mock(WebClient.class);
    WebClient.RequestBodyUriSpec requestBodyUriSpec = mock(WebClient.RequestBodyUriSpec.class);
    WebClient.RequestHeadersSpec requestHeadersSpec = mock(WebClient.RequestHeadersSpec.class);
    WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class);

    when(webClient.post()).thenReturn(requestBodyUriSpec);
    when(requestBodyUriSpec.uri(anyString())).thenReturn(requestBodyUriSpec);
    when(requestBodyUriSpec.bodyValue(any(Type.class))).thenReturn(requestHeadersSpec);
    when(requestHeadersSpec.headers(any(Consumer.class))).thenReturn(requestHeadersSpec);
    when(requestHeadersSpec.retrieve()).thenReturn(responseSpec);
    when(responseSpec.bodyToMono(Void.class)).thenReturn(Mono.empty());

    // 注入模拟的WebClient
    trelloService.setWebClient(webClient);

    // 执行测试
    trelloService.trelloCallback(new Type());

    // 验证各个方法是否被正确调用
    verify(webClient).post();
    verify(requestBodyUriSpec).uri(URL);
    verify(requestBodyUriSpec).bodyValue(any(Type.class));
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 14:45:33