Spring Boot Reactive WebClient结合Pact处理404场景的测试问题
问题
在Spring Boot Reactive场景下结合JUnit5(Model2)实现Pact消费者测试,编写查询ID为10的不存在产品的用例时,预期触发HttpClientErrorException但测试失败,提示MockServer未收到请求,无法生成Pact文件。
测试代码
@Pact(consumer = "ProductCatalogue") public RequestResponsePact singleProductNotExists(PactDslWithProvider builder) { return builder .given("product with ID 10 does not exist", "id", 10) .uponReceiving("get product with ID 10") .path("/product/10") .willRespondWith() .headers(Map.of("Content-Type", "application/json")) .status(404) .toPact(); } @Test @PactTestFor(pactMethod = "singleProductNotExists", pactVersion = PactSpecVersion.V3) void testSingleProductNotExists(MockServer mockServer) { WebClient webClient = WebClient.builder().baseUrl(mockServer.getUrl()).build(); productService = new ProductService(webClient); try { productService.getProductById(10); fail("Expected service call to throw an exception"); } catch (HttpClientErrorException ex) { assertThat(ex.getMessage(), containsString("404 Not Found")); } }
错误信息
[INFO] [ERROR] Failures: [ERROR] ProductServiceClientPactTest.testSingleProductNotExists:190 Expected service call to throw an exception [INFO] [ERROR] Tests run: 4, Failures: 1, Errors: 0, Skipped: 0 [ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 1.896 s <<< FAILURE! -- in com.example.ProductServiceClientPactTest [ERROR] com.example.ProductServiceClientPactTest.testSingleProductNotExists(MockServer) -- Time elapsed: 1.805 s <<< FAILURE! java.lang.AssertionError: Expected service call to throw an exception at com.example.ProductServiceClientPactTest.testSingleProductNotExists(ProductServiceClientPactTest.java:190) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) Suppressed: au.com.dius.pact.consumer.PactMismatchesException: The following requests were not received: method: GET path: /product/10 query: {} headers: {} matchers: MatchingRules(rules={}) generators: Generators(categories={}) body: MISSING at au.com.dius.pact.consumer.junit.JUnitTestSupport.validateMockServerResult(JUnitTestSupport.kt:110) at au.com.dius.pact.consumer.junit5.PactConsumerTestExt.afterTestExecution(PactConsumerTestExt.kt:631) ... 2 more
解决方案
核心问题是响应式代码的惰性执行特性:WebClient返回的Mono/Flux不会在调用方法时立即发起请求,也不会同步抛出异常,必须通过订阅或阻塞操作触发请求执行。
1. 修正测试的调用逻辑
修改测试代码,对响应式结果进行阻塞调用,确保请求真正发送到MockServer:
@Test @PactTestFor(pactMethod = "singleProductNotExists", pactVersion = PactSpecVersion.V3) void testSingleProductNotExists(MockServer mockServer) { WebClient webClient = WebClient.builder().baseUrl(mockServer.getUrl()).build(); productService = new ProductService(webClient); try { // 用block()触发请求执行,同步获取结果或抛出异常 productService.getProductById(10).block(); fail("Expected service call to throw an exception"); } catch (WebClientResponseException.NotFound ex) { // WebClient默认抛出WebClientResponseException子类,而非HttpClientErrorException assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); } }
2. 确保Service层正确传递错误
检查ProductService的实现,确保没有吞掉响应式流中的错误:
public class ProductService { private final WebClient webClient; public ProductService(WebClient webClient) { this.webClient = webClient; } public Mono<Product> getProductById(Integer id) { return webClient.get() .uri("/product/{id}", id) .retrieve() // 不自定义onStatus的话,4xx/5xx会自动抛出WebClientResponseException .bodyToMono(Product.class); } }
3. 异常类型适配(可选)
如果必须捕获HttpClientErrorException,可以在Service层自定义错误处理:
public Mono<Product> getProductById(Integer id) { return webClient.get() .uri("/product/{id}", id) .retrieve() .onStatus(HttpStatus::is4xxClientError, response -> response.bodyToMono(String.class) .map(body -> new HttpClientErrorException(response.statusCode(), body)) ) .bodyToMono(Product.class); }
4. 验证请求匹配一致性
确认Pact定义的请求路径、方法和实际调用完全一致,避免因路径前缀、参数格式差异导致MockServer无法识别请求。
内容的提问来源于stack exchange,提问作者Kris Swat
相关产品推荐
相关产品推荐

