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

Spring Retry测试时重试次数比配置多1次,求问题排查及测试方案

问题1:测试调用次数不匹配的原因

  1. 多余的启动自动调用
    你给FakeConfiguration实现了ApplicationRunner接口,在@SpringBootTest启动Spring上下文时,会自动执行run()方法触发runParser()的重试逻辑,此时你的Mock已经生效,这一步就已经调用了5次add()方法;后续你在测试方法中手动调用fakeConfiguration.runParser()又触发了1次调用,总次数正好是报错中的6次。
  2. 注解使用位置不规范
    不要将@Retryable标注在@Configuration配置类的方法上,配置类本身是通过CGLIB代理实现的,容易和Spring Retry的AOP代理逻辑产生冲突,重试逻辑建议放到独立的@Component/@Service类中。
  3. 参数说明补充
    @Retryable的maxAttempts参数本身包含第一次正常调用,配置为5就是1次初始调用+4次重试,总共调用5次,这个参数定义是符合你的预期的。

问题2:Spring Retry的测试方式说明

声明式注解(@Retryable)场景

这类重试逻辑基于Spring AOP代理实现,必须由Spring上下文管理Bean生成代理对象才能生效,因此需要启动Spring上下文,属于集成测试范畴。

编程式RetryTemplate场景

如果不想启动Spring上下文做单元测试,可以直接使用编程式的RetryTemplate,手动配置重试规则后直接调用,不需要依赖Spring容器,可直接在普通单元测试中验证重试逻辑。


修复后的代码示例

1. 把重试逻辑移到独立服务类

@Service
public class ParserService {
    private final FakeParser fakeParser;

    public ParserService(FakeParser fakeParser) {
        this.fakeParser = fakeParser;
    }

    @Retryable(maxAttempts = 5, value = RuntimeException.class)
    public void runParser() {
        fakeParser.add();
    }
}

2. 调整配置类

@Configuration
@EnableRetry
public class FakeConfiguration {
    // 去掉ApplicationRunner实现,避免启动时自动调用重试逻辑
}

3. 调整测试类

@RunWith(SpringRunner.class)
@SpringBootTest
class SpringtestApplicationTests {

  @Autowired
  private ParserService parserService;

  @MockBean
  private FakeParser fakeParser;

  @Test
  public void retry5times(){
    when(fakeParser.add()).thenThrow(RuntimeException.class);

    try {
      parserService.runParser();
    } catch (RuntimeException e){
      // 忽略预期异常
    }

    verify(fakeParser, times(5)).add(); // 验证通过
  }
}

无Spring上下文的单元测试示例(编程式RetryTemplate)

public class RetryUnitTest {
    @Test
    public void testRetryWithoutSpring() {
        FakeParser fakeParser = Mockito.mock(FakeParser.class);
        when(fakeParser.add()).thenThrow(RuntimeException.class);

        // 手动配置重试规则
        RetryTemplate retryTemplate = RetryTemplate.builder()
                .maxAttempts(5)
                .retryOn(RuntimeException.class)
                .build();
        
        try {
            retryTemplate.execute(context -> {
                fakeParser.add();
                return null;
            });
        } catch (RuntimeException e) {
            // 忽略预期异常
        }

        verify(fakeParser, times(5)).add(); // 验证通过
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 17:06:02