如何用Mockito正确测试带有.retry()的RxJava 2方法?
我能立刻明白你遇到的问题——这是RxJava的retry()操作符和Mockito行为之间的常见坑!让我一步步拆解原因,再给你两种适配不同场景的解决方案:
问题根源
你代码里的bar.dangerousMethod().retry(10)有个关键细节:retry()是对bar.dangerousMethod()返回的单个Completable实例进行重复订阅,而不是每次重试都重新调用bar.dangerousMethod()。
你看到的“dangerousMethod()被执行了10次”其实是误解:实际是这个方法返回的Completable被订阅了10次,而dangerousMethod()本身只被调用了1次。你的Mockito设置:
when(bar.dangerousMethod()).thenReturn( error(new Exception()), error(new Exception()), complete() );
只会在**第一次调用bar.dangerousMethod()**时返回第一个错误Completable,后续的重试只是重复订阅这个同一个Completable(每次订阅都会抛出错误),所以会触发全部10次重试,最终整个流以失败告终,导致assertComplete()断言失败。
解决方案一:修改生产代码(推荐,符合重试语义)
如果你的业务逻辑是每次重试都需要重新执行dangerousMethod()(比如这个方法包含有状态操作或需要重新发起请求),那么需要用Completable.defer()包装方法调用,确保每次重试都会触发新的方法调用:
修改Foo类的execute()方法:
public Completable execute() { return Completable.fromAction(() -> System.out.println("first part of task")) // 用defer包装,每次重试都会调用bar.dangerousMethod() .andThen(Completable.defer(() -> bar.dangerousMethod()) .retry(10) ) .andThen(Completable.fromAction(() -> System.out.println("sec part of task"))); }
修改后,你的原测试代码就能正常工作:前两次调用bar.dangerousMethod()返回错误,第三次返回成功,重试两次后流就会完成,断言通过。
解决方案二:不修改生产代码,调整Mockito测试逻辑
如果你不想修改生产代码,只是想测试当前的retry()行为(重复订阅同一个Completable),可以用Mockito的thenAnswer()让返回的Completable根据订阅次数动态返回错误或成功:
@Test public void test() throws Exception { AtomicInteger subscribeCount = new AtomicInteger(0); when(bar.dangerousMethod()).thenAnswer(invocation -> { int count = subscribeCount.incrementAndGet(); // 前两次订阅返回错误,第三次返回成功 if (count <= 2) { return Completable.error(new Exception("Retry failed")); } else { return Completable.complete(); } }); foo.execute() .test() .assertComplete(); }
这里用AtomicInteger跟踪订阅次数,Mock返回的Completable会根据订阅次数决定行为:前两次订阅抛出错误,第三次订阅成功,retry(10)会在第三次订阅时成功,整个流完成,断言通过。
额外验证(可选)
你可以添加验证来确认行为是否符合预期:
// 方案一(修改生产代码):验证bar.dangerousMethod()被调用3次 verify(bar, times(3)).dangerousMethod(); // 方案二(不修改生产代码):验证bar.dangerousMethod()只被调用1次 verify(bar, times(1)).dangerousMethod();
内容的提问来源于stack exchange,提问作者Lau

