无法注入Mock Runnable至服务调用,Mockito验证重试次数失败
问题分析与解决方案
核心问题
你的测试代码存在两个关键错误,导致mock的Runnable从未被调用:
- 参数匹配不匹配:你在
doAnswer中指定了固定参数"test"和mockRunnable,但实际ServiceA.getData()调用callRunnableWithRetry时,传入的是obj.getName()(值为"test-value")和一个匿名Runnable实例,和你mock的mockRunnable完全不是同一个对象,所以这个Stub逻辑根本没触发。 - Stub逻辑不符合实际代码流程:
RetryService.callRunnableWithRetry的设计是返回装饰后的CheckedRunnable,由调用方(也就是ServiceA)去执行run()方法,但你的Stub直接在内部执行了checkedRunnable.run(),而没有返回这个装饰后的对象,和真实代码的执行流程不一致。
修正后的测试代码(直接追踪调用次数)
@Test void testRetry(){ RetryService retryServiceSpy = spy(retryService); ObjectA obj = new ObjectA("test-value"); // 自定义带计数和异常逻辑的Runnable AtomicInteger callCount = new AtomicInteger(0); Runnable testRunnable = () -> { callCount.incrementAndGet(); // 前两次调用抛异常,第三次正常执行 if (callCount.get() <= 2) { throw new RuntimeException("Simulated exception"); } }; // 拦截callRunnableWithRetry,替换传入的Runnable为我们的测试实例 doAnswer(invocation -> { String configName = invocation.getArgument(0); Retry retry = retryServiceSpy.getRetry(configName); // 装饰测试用Runnable并返回,保持原方法的返回逻辑 return Retry.decorateCheckedRunnable(retry, testRunnable::run); }).when(retryServiceSpy).callRunnableWithRetry(eq(obj.getName()), any(Runnable.class)); // 执行测试方法 serviceA.getData(obj); // 验证总调用次数是否为3次 assertThat(callCount.get()).isEqualTo(3); }
可选方案:通过Retry事件验证重试逻辑
如果你不想修改Runnable的行为,也可以通过监听Resilience4j的Retry事件来确认重试是否触发:
@Test void testRetry(){ RetryService retryServiceSpy = spy(retryService); ObjectA obj = new ObjectA("test-value"); AtomicInteger retryEventCount = new AtomicInteger(0); // 拦截Retry实例的创建,监听重试事件 doAnswer(invocation -> { Retry retry = invocation.callRealMethod(); // 每触发一次重试,计数器+1 retry.getEventPublisher().onRetry(event -> retryEventCount.incrementAndGet()); return retry; }).when(retryServiceSpy).getRetry(eq(obj.getName())); // 执行测试方法 serviceA.getData(obj); // 验证重试次数是否为2次(总调用3次,重试2次) assertThat(retryEventCount.get()).isEqualTo(2); }
内容的提问来源于stack exchange,提问作者frlzjosh
相关产品推荐
相关产品推荐

