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

如何验证ExchangeFilterFunction中的重试已耗尽?

问题:如何程序化验证WebClient重试过滤器的重试次数?

我实现了一个作为WebClient全局重试机制的ExchangeFilterFunction,过滤器代码如下:

@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
    return next.exchange(request)
            .flatMap(clientResponse -> Mono.just(clientResponse)
                    .filter(response -> {
                                final HttpStatusCode code = clientResponse.statusCode();
                                boolean isRetryable = Boolean.FALSE;
                                if (code.isError()) {
                                    //check if its a retryable error
                                    isRetryable = Arrays.stream(DEFAULT_RETRYABLE_ERROR_CODES).anyMatch(defaultCode -> defaultCode == code.value()) ||
                                            (retryFilterConfiguration.getRetryErrorCodes() != null &&
                                                    retryFilterConfiguration.getRetryErrorCodes().stream().anyMatch(retryErrorCode -> retryErrorCode == code.value()));
                                    LOGGER.warn("Request Failed.  Retrying -> url={}; status={}", request.url(), code.value());
                                }
                                return isRetryable;
                            }
                    ).flatMap(response -> clientResponse.createException())
                    .flatMap(Mono::error)
                    .thenReturn(clientResponse)
            ).retry(retryFilterConfiguration.getRetryCount());
}

我编写了单元测试来验证重试已耗尽,测试能确认错误发生,但无法通过代码验证重试次数,仅能通过日志观察重试行为,测试代码如下:

@DisplayName("Retry on Default Error Code")
@Test
public void defaultRetryableErrorCode(){
    //ARRANGE
    int retryCount = 2;
    int tooManyRequestsErrorCode = HttpStatus.TOO_MANY_REQUESTS.value();
    ClientRequest mockRequest = mock(ClientRequest.class);
    ExchangeFunction mockNext = mock(ExchangeFunction.class);
    ClientResponse mockResponse = mock(ClientResponse.class);
    when(mockNext.exchange(mockRequest)).thenReturn(Mono.just(mockResponse));
    when(mockResponse.statusCode()).thenReturn(HttpStatusCode.valueOf(tooManyRequestsErrorCode))
            .thenReturn(HttpStatusCode.valueOf(tooManyRequestsErrorCode))
            .thenReturn(HttpStatusCode.valueOf(tooManyRequestsErrorCode));
    when(retryFilterConfiguration.getRetryCount()).thenReturn(retryCount);
    //ACT
    Mono<ClientResponse> clientResponseMono = retryStrategyFilter.filter(mockRequest,mockNext);
    //ASSERT
    StepVerifier.create(clientResponseMono)
            .verifyError();
}

请问如何通过程序化方式验证重试已耗尽?


解决方案

可以通过以下几种直接的方式程序化验证重试次数:

1. 用Mockito验证方法调用次数

ExchangeFunction.exchange()是每次重试都会触发的方法,直接验证它的调用次数即可。修改测试的断言部分:

// 原有的错误验证
StepVerifier.create(clientResponseMono)
        .verifyError();

// 验证exchange方法总调用次数:初始请求1次 + 重试次数retryCount
verify(mockNext, times(retryCount + 1)).exchange(mockRequest);

注意:retry(n)会在失败后重试n次,所以总调用次数是1 + n,和你之前mock的3次错误返回完全匹配。

2. 自定义计数器追踪调用次数

如果不想依赖Mockito,可自己实现带计数功能的ExchangeFunction:

// 在ARRANGE阶段创建计数器和自定义ExchangeFunction
AtomicInteger callCount = new AtomicInteger(0);
ExchangeFunction countingExchangeFunction = request -> {
    callCount.incrementAndGet();
    return Mono.just(mockResponse);
};

// 替换原有的mockNext逻辑,无需再mock exchange方法
// when(mockNext.exchange(mockRequest)).thenReturn(Mono.just(mockResponse));

// ACT阶段使用自定义的ExchangeFunction
Mono<ClientResponse> clientResponseMono = retryStrategyFilter.filter(mockRequest, countingExchangeFunction);

// ASSERT阶段验证计数器值
StepVerifier.create(clientResponseMono)
        .verifyError();

assertEquals(retryCount + 1, callCount.get());

注意事项

  • 确保mock的错误返回次数和总调用次数一致,否则会抛出NoSuchElementException;
  • 如果重试逻辑包含延迟(比如用retryWhen配合退避策略),需要在StepVerifier中添加thenAwait()等待延迟完成。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 17:17:30