如何测试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
相关产品推荐
相关产品推荐

