如何使用WireMock测试WebClient?分页删除逻辑测试异常求助
问题:分页数据删除逻辑的WireMock测试失败,删除端点未触发
我实现了一段逻辑:通过client1获取指定客户的分页数据,再调用client2的端点删除这些数据。但用WireMock测试时发现删除端点从未被触发,调试后发现代码根本没走到调用删除端点的部分,不确定是不是WireMock的测试方式有问题,附上实现代码和测试代码,求解决办法。
实现代码
public void deleteItems(String id) { client1.get() .uri(urlBuilder -> urlBuilder .path(CLIENT_ONE_ENDPOINT) .queryParam("email", id) .build()) .retrieve() .bodyToMono(Items.class) .expand(items -> { while (!items.isOnLastPage()) { int nextPage = items.getCurrentPage() + 1; return client1.get() .uri(builder -> builder .path(CLIENT_ONE_ENDPOINT) .queryParam("email", id) .queryParam("pageNumber", nextPage) .build()) .retrieve() .bodyToMono(Items.class); } return Flux.empty(); }) .flatMapIterable(Items::getResults) .map(or -> { return client2.delete() .uri(CLIENT_TWO_ENDPOINT + or.getId()) .retrieve() .bodyToMono(ResponseEntity.class) .subscribe(); }) .subscribe(); }
WireMock测试代码
@Test void itemsAreRemoved() { String email = "example@example.com"; String itemId = "001"; WireMock.configureFor(PORT); wireMockServer.stubFor(get(urlPathEqualTo(CLIENT_ONE_ENDPOINT)) .withQueryParam("email", equalTo(email)) .withHeader("Content-Type", equalTo("application/json")) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody(loadResponse(itemId)) .withStatus(200))); wireMockServer.stubFor(get(urlPathEqualTo(CLIENT_ONE_ENDPOINT)) .withQueryParam("email", equalTo(email)) .withQueryParam("pageNumber", equalTo("1")) .withHeader("Content-Type", equalTo("application/json")) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withStatus(200))); wireMockServer.stubFor(delete(urlPathEqualTo(CLIENT_TWO_ENDPOINT + itemId))); service.deleteItems(email); verify(getRequestedFor(urlPathEqualTo(CLIENT_ONE_ENDPOINT)) .withQueryParam("email", equalTo(email))); verify(getRequestedFor(urlPathEqualTo(CLIENT_ONE_ENDPOINT)) .withQueryParam("email", equalTo(email)) .withQueryParam("pageNumber", equalTo("1"))); verify(exactly(1), deleteRequestedFor(urlPathEqualTo(CLIENT_TWO_ENDPOINT + itemId))); }
问题分析与修改方案
核心问题点
- 反应式流的订阅逻辑错误:原代码中
map里的删除操作调用了.subscribe(),导致异步操作脱离主数据流,外层的.subscribe()不会等待删除操作完成,测试线程可能直接进入verify环节。 expand的循环逻辑错误:while循环不适合expand操作——expand是每次处理完当前元素后生成下一个元素,while会导致逻辑卡死或只获取一次下一页。- 测试未等待异步操作完成:调用
deleteItems后直接执行verify,反应式操作还没执行完就断言,必然失败。 - WireMock Stub的匹配条件冗余:GET请求的Stub要求
Content-Type头,但实际client1的GET请求可能没带这个头,导致Stub不匹配,根本拿不到数据,自然不会触发删除。
修改后的实现代码
public Mono<Void> deleteItems(String id) { return client1.get() .uri(urlBuilder -> urlBuilder .path(CLIENT_ONE_ENDPOINT) .queryParam("email", id) .build()) .retrieve() .bodyToMono(Items.class) .expand(items -> { // 替换while为if,符合expand的工作机制 if (!items.isOnLastPage()) { int nextPage = items.getCurrentPage() + 1; return client1.get() .uri(builder -> builder .path(CLIENT_ONE_ENDPOINT) .queryParam("email", id) .queryParam("pageNumber", nextPage) .build()) .retrieve() .bodyToMono(Items.class); } return Mono.empty(); }) .flatMapIterable(Items::getResults) // 用flatMap替代map,将删除操作融入主数据流,不手动订阅 .flatMap(or -> client2.delete() .uri(CLIENT_TWO_ENDPOINT + or.getId()) .retrieve() .bodyToMono(Void.class)) // 用then()标记流结束,返回Mono<Void>方便测试等待 .then(); }
修改后的测试代码
@Test void itemsAreRemoved() { String email = "example@example.com"; String itemId = "001"; // 去掉GET请求对Content-Type头的匹配(如果实际请求确实带这个头可以保留) wireMockServer.stubFor(get(urlPathEqualTo(CLIENT_ONE_ENDPOINT)) .withQueryParam("email", equalTo(email)) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody(loadResponse(itemId)) .withStatus(200))); wireMockServer.stubFor(get(urlPathEqualTo(CLIENT_ONE_ENDPOINT)) .withQueryParam("email", equalTo(email)) .withQueryParam("pageNumber", equalTo("1")) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withStatus(200))); // 给删除Stub添加返回状态码,确保client2调用能正常完成 wireMockServer.stubFor(delete(urlPathEqualTo(CLIENT_TWO_ENDPOINT + itemId)) .willReturn(aResponse().withStatus(204))); // 调用block()等待反应式流完全执行完毕 service.deleteItems(email).block(); verify(getRequestedFor(urlPathEqualTo(CLIENT_ONE_ENDPOINT)) .withQueryParam("email", equalTo(email))); verify(getRequestedFor(urlPathEqualTo(CLIENT_ONE_ENDPOINT)) .withQueryParam("email", equalTo(email)) .withQueryParam("pageNumber", equalTo("1"))); verify(exactly(1), deleteRequestedFor(urlPathEqualTo(CLIENT_TWO_ENDPOINT + itemId))); }
内容的提问来源于stack exchange,提问作者Minimum
相关产品推荐
相关产品推荐

