
本文介绍在 quarkus 或 mutiny 环境下,如何可靠地测试那些直接订阅 uni 并执行副作用(如日志记录)但不返回响应的 void 方法,解决因异步执行导致的竞态条件问题。
在使用 Mutiny 的响应式编程实践中,一个常见痛点是:当业务方法(如 execute())内部调用 uni.subscribe().with(...) 并仅执行副作用(例如打日志、更新状态),而自身返回 void 时,该方法在单元测试中极易因异步调度而出现竞态失败——断言在 Uni 实际完成前就已执行,导致 Mockito.verify() 检查失败。
直接使用 Thread.sleep(1000)(如答案中所示)虽能“凑效”,但属于反模式:它使测试变慢、不可靠(时间阈值难适配不同环境)、且掩盖了设计缺陷。更专业、可维护的解决方案应兼顾即时性、确定性与可测性。
✅ 推荐方案一:注入可控的 Uni + 使用 awaitility(推荐)
Awaitility 是专为异步断言设计的轻量库,支持声明式等待条件,语义清晰、超时可控、线程安全:
org.awaitility awaitility test
测试示例:
@Test
public void shouldLogSuccessAfterReprocessing() {
// 给 service 注入一个立即完成的 Uni(例如通过 Mockito mock)
Uni completedUni = Uni.createFrom().voidItem();
when(service.reprocessAll()).thenReturn(completedUni);
service.execute();
// 等待日志被调用一次,最多等待 3 秒,每 100ms 检查一次
await()
.atMost(3, TimeUnit.SECONDS)
.pollInterval(100, TimeUnit.MILLISECONDS)
.untilAsserted(() ->
Mockito.verify(log, times(1)).info("Reprocessing ran successfully.")
);
} ⚠️ 注意:确保 log 是被 @Mock 的真实 mock 对象,且 service 是通过依赖注入或构造器注入的可替换实例。
本文档主要讲述的是maven使用方法;Maven是基于项目对象模型的(pom),可以通过一小段描述信息来管理项目的构建,报告和文档的软件项目管理工具。Maven将你的注意力从昨夜基层转移到项目管理层。Maven项目已经能够知道 如何构建和捆绑代码,运行测试,生成文档并宿主项目网页。希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看
✅ 推荐方案二:重构为可组合接口(长期最佳实践)
根本解法是将副作用分离并返回可测试的信号。修改 execute() 使其返回 Uni
public Uniexecute() { return service.reprocessAll() .onItem().invoke(v -> log.info("Reprocessing ran successfully.")) .onFailure().invoke(t -> log.severe("Reprocessing failed: " + t.getMessage())) .replaceWithVoid(); // 返回 Uni 表示“执行完成” }
此时测试变得简洁、同步、无竞态:
@Test
public void shouldLogSuccessAfterReprocessing() {
when(service.reprocessAll()).thenReturn(Uni.createFrom().voidItem());
service.execute()
.subscribe()
.withSubscriber(UniAssertSubscriber.create())
.assertCompleted();
Mockito.verify(log, times(1)).info("Reprocessing ran successfully.");
}? 总结
- ❌ 避免 Thread.sleep():非确定性、低效、易误判;
- ✅ 优先用 Awaitility:适合短期适配遗留 void 方法;
- ✅ 更优是重构为返回 Uni:符合响应式契约,天然可链式断言,提升代码内聚性与可观测性;
- ? 测试前提:确保被测对象依赖可 mock(如 service, log),并采用真正的异步执行环境(如 Mutiny 默认的 io.smallrye.mutiny.infrastructure.Infrastructure.getDefaultExecutor())。
通过以上方式,你既能快速修复当前测试失败,又能逐步演进代码向更健壮、可维护的响应式设计靠拢。









