
本文介绍使用 Mutiny 的 Uni 实现重试逻辑时,如何编写可靠单元测试——关键在于避免 Mockito 返回静态 Uni 实例,而应通过 Uni.createFrom().deferred() 动态生成不同结果,确保每次订阅触发新行为。
本文介绍使用 mutiny 的 `uni` 实现重试逻辑时,如何编写可靠单元测试——关键在于避免 mockito 返回静态 uni 实例,而应通过 `uni.createfrom().deferred()` 动态生成不同结果,确保每次订阅触发新行为。
在 Quarkus + Mutiny 应用中,为 REST 客户端调用添加指数退避重试(如 onFailure().retry().withBackOff(...).atMost(5))是常见实践。但测试这类逻辑时,一个典型陷阱是:误用 Mockito.when(...).thenReturn(un1, un2) 期望 Mockito 自动按顺序返回多个 Uni 实例——而实际上,Mockito 的 thenReturn 对于 同一个方法调用 的多次订阅(即重试触发的重复 fetchData() 调用)并不会自动“推进”到下一个值,除非该方法被真实调用多次。
问题根源在于:restClient.fetchData() 在重试过程中会被多次调用,但若你用 thenReturn(Uni.fail(...), Uni.item(...)),Mockito 默认仅对 首次调用 返回第一个值;后续调用若未显式配置,可能复用同一 Uni 实例(尤其当该 Uni 是冷信号且未被重新创建),导致重试始终失败。
✅ 正确解法是:让 fetchData() 每次被调用时都返回一个全新的、行为可变的 Uni —— 这正是 Uni.createFrom().deferred(Supplier 的用途。它延迟执行 Supplier,在每次订阅时生成新 Uni,从而支持模拟“首次失败、第二次成功”的重试场景。
以下是推荐的测试实现:
@Test
public void shouldRetryOnFailure() {
MyRestClient restClient = Mockito.mock(MyRestClient.class);
MyService myService = new MyService();
myService.restClient = restClient;
AtomicInteger callCount = new AtomicInteger(0);
Mockito.when(restClient.fetchData())
.thenReturn(Uni.createFrom().deferred(() -> {
int count = callCount.incrementAndGet();
if (count == 1) {
return Uni.createFrom().failure(new RuntimeException("Transient failure"));
} else {
return Uni.createFrom().item(Response.ok().build());
}
}));
// 执行并等待完成(注意:生产环境应避免 await().indefinitely(),测试中可接受)
Response result = myService.fetchData().await().indefinitely();
// 验证:成功获取响应,且恰好调用两次
assertThat(result.getStatus()).isEqualTo(200);
assertThat(callCount.get()).isEqualTo(2);
// 注意:verify 仍应检查 times(2),因为 deferred 确保了两次独立调用
Mockito.verify(restClient, Mockito.times(2)).fetchData();
}
? 关键注意事项:
-
Uni.createFrom().deferred(...)是核心——它确保每次fetchData()被调用时都执行一次 Supplier,从而支持状态感知的行为(如计数器)。 -
Mockito.verify(restClient, times(2))必须成立,否则说明重试未真正触发新调用(可能是代码未正确链式调用.onFailure().retry(),或被意外缓存)。 - 避免在测试中使用
await().indefinitely()以外的阻塞方式;如需超时控制,可用await().atMost(Duration.ofSeconds(10))。 - 若需更复杂重试序列(如失败3次后成功),只需扩展
callCount的条件分支即可。
通过这种基于 deferred 的动态 Uni 构造方式,你的测试能真实反映重试机制的行为,既验证了异常恢复能力,也确保了客户端调用次数符合预期——这才是 Mutiny 异步重试测试的可靠范式。










