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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 13:57:29