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

带SocketTimeout重试的Reactor流单元测试:重试逻辑未触发排查

Reactor重试逻辑在JUnit测试中不触发的问题排查

问题描述

我编写了一个调用foobarMethod的Reactor流,该方法可能抛出SocketTimeoutException类型的Mono异常。预期触发异常时重试2次,手动运行代码时功能正常,但JUnit测试中无法触发重试逻辑。请问这是单元测试环境问题还是测试代码编写有误?

服务代码

public void foobarServiceMethod() {
   foobarClient.foobarMethod(foobarParameter)
            .onErrorMap(ex -> {
                log.error("Code has failed");
                if (ex instanceof SocketTimeoutException) {
                    return new SocketTimeoutException("");
                } else {
                    return ex;
                }
            })
            .retryWhen(
                    Retry.fixedDelay(3, Duration.ofSeconds(1))
                            .filter(SocketTimeoutException.class::isInstance))
            .subscribe(foobarResponse -> handleFoobarResoponse(foobarResponse));
}

测试代码

@Test
void foobarRest(CapturedOutput capturedOutput) throws SocketTimeoutException, InterruptedException {
    foobarParameter = "test";
    when(foobarClient.foobarMethod(any()))
            .thenReturn(Mono.error(new SocketTimeoutException("First timeout")))
            .thenReturn(Mono.error(new SocketTimeoutException("First timeout")));

    foobarMethod(foobarParameter);

    assertEquals(2, capturedOutputSplit.contains("Code has failed"));
}

问题排查与修复

你的问题出在测试代码编写错误,和环境无关,核心问题有三个:

1. 异步流未等待执行完成

Reactor流是异步执行的,测试方法调用foobarMethod后直接进入断言逻辑,此时重试流程可能还没执行完,测试就已经结束了,导致日志未完全打印。

2. Mock返回的错误次数不匹配

你配置的Retry.fixedDelay(3, Duration.ofSeconds(1))意味着会重试3次,加上初始调用总共会发起4次请求,但你的Mock只返回了2次错误,无法触发完整的重试流程。如果你的预期是重试2次(总调用3次),应该把重试次数改为2。

3. 断言逻辑错误

capturedOutputSplit.contains("Code has failed")返回的是布尔值,不能直接和数字2做相等判断,你需要统计该字符串在输出中出现的实际次数。


修正后的测试代码

方式1:手动等待(简单但不够优雅)

@Test
void foobarRest(CapturedOutput capturedOutput) throws InterruptedException {
    foobarParameter = "test";
    // 模拟3次错误(对应重试2次,总调用3次)
    when(foobarClient.foobarMethod(any()))
            .thenReturn(Mono.error(new SocketTimeoutException("First timeout")))
            .thenReturn(Mono.error(new SocketTimeoutException("Second timeout")))
            .thenReturn(Mono.error(new SocketTimeoutException("Third timeout")));

    foobarServiceMethod();
    // 等待重试流程完成(2次重试+初始调用,间隔1秒,留冗余时间)
    Thread.sleep(3000);

    // 统计错误日志出现次数
    long errorCount = Arrays.stream(capturedOutput.getOut().split("\n"))
            .filter(line -> line.contains("Code has failed"))
            .count();
    assertEquals(3, errorCount); // 初始调用+2次重试,共3次错误日志
}

方式2:使用StepVerifier(Reactor测试标准方式)

先修改服务方法,让它返回Mono而非直接subscribe,方便测试:

public Mono<FoobarResponse> foobarServiceMethod() {
   return foobarClient.foobarMethod(foobarParameter)
            .onErrorMap(ex -> {
                log.error("Code has failed");
                if (ex instanceof SocketTimeoutException) {
                    return new SocketTimeoutException("");
                } else {
                    return ex;
                }
            })
            .retryWhen(
                    Retry.fixedDelay(2, Duration.ofSeconds(1)) // 重试2次,总调用3次
                            .filter(SocketTimeoutException.class::isInstance));
}

测试方法:

@Test
void foobarRest(CapturedOutput capturedOutput) {
    foobarParameter = "test";
    when(foobarClient.foobarMethod(any()))
            .thenReturn(Mono.error(new SocketTimeoutException("First timeout")))
            .thenReturn(Mono.error(new SocketTimeoutException("Second timeout")))
            .thenReturn(Mono.error(new SocketTimeoutException("Third timeout")));

    // 使用StepVerifier订阅并验证流的执行
    StepVerifier.create(foobarServiceMethod())
            .expectError(SocketTimeoutException.class)
            .verify(Duration.ofSeconds(3)); // 等待流执行完成

    // 统计错误日志次数
    long errorCount = Arrays.stream(capturedOutput.getOut().split("\n"))
            .filter(line -> line.contains("Code has failed"))
            .count();
    assertEquals(3, errorCount);
}

内容的提问来源于stack exchange,提问作者Hywel Griffiths

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 14:52:49