如何测试订阅 Uni 的 void 方法
发布时间 - 2026-01-22 00:00:00 点击率:次本文介绍在 quarkus 或 mutiny 环境下,如何可靠地测试那些直接订阅 uni 并执行副作用(如日志记录)但不返回响应的 void 方法,解决因异步执行导致的竞态条件问题。
在使用 Mutiny 的响应式编程实践中,一个常见痛点是:当业务方法(如 execute())内部调用 uni.subscribe().with(...) 并仅执行副作用(例如打日志、更新状态),而自身返回 void 时,该方法在单元测试中极易因异步调度而出现竞态失败——断言在 Uni 实际完成前就已执行,导致 Mockito.verify() 检查失败。
直接使用 Thread.sleep(1000)(如答案中所示)虽能“凑效”,但属于反模式:它使测试变慢、不可靠(时间阈值难适配不同环境)、且掩盖了设计缺陷。更专业、可维护的解决方案应兼顾即时性、确定性与可测性。
✅ 推荐方案一:注入可控的 Uni + 使用 awaitility(推荐)
Awaitility 是专为异步断言设计的轻量库,支持

org.awaitility awaitilitytest
测试示例:
@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 是通过依赖注入或构造器注入的可替换实例。
✅ 推荐方案二:重构为可组合接口(长期最佳实践)
根本解法是将副作用分离并返回可测试的信号。修改 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())。
通过以上方式,你既能快速修复当前测试失败,又能逐步演进代码向更健壮、可维护的响应式设计靠拢。
# ai
# 响应式设计
# 响应式编程
# quark
# void
# 接口
# 线程
# Thread
# 对象
# 异步
# 重构
# 链式
# 最多
# 使其
# 又能
# 所示
# 而非
# 专为
# 但不
# 前就
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Laravel如何实现全文搜索功能?(Scout和Algolia示例)
如何为不同团队 ID 动态生成多个非值班状态按钮
Windows10电脑怎么查看硬盘通电时间_Win10使用工具检测磁盘健康
5种Android数据存储方式汇总
网站优化排名时,需要考虑哪些问题呢?
无锡营销型网站制作公司,无锡网选车牌流程?
用v-html解决Vue.js渲染中html标签不被解析的问题
Laravel怎么判断请求类型_Laravel Request isMethod用法
如何在腾讯云服务器快速搭建个人网站?
如何制作新型网站程序文件,新型止水鱼鳞网要拆除吗?
网页制作模板网站推荐,网页设计海报之类的素材哪里好?
Python3.6正式版新特性预览
如何快速搭建高效可靠的建站解决方案?
Laravel的HTTP客户端怎么用_Laravel HTTP Client发起API请求教程
Laravel如何编写单元测试和功能测试?(PHPUnit示例)
Laravel怎么实现前端Toast弹窗提示_Laravel Session闪存数据Flash传递给前端【方法】
JavaScript中的标签模板是什么_它如何扩展字符串功能
Laravel项目结构怎么组织_大型Laravel应用的最佳目录结构实践
Android实现代码画虚线边框背景效果
详解CentOS6.5 安装 MySQL5.1.71的方法
Laravel如何实现API版本控制_Laravel API版本化路由设计策略
如何在Tomcat中配置并部署网站项目?
Laravel Sail是什么_基于Docker的Laravel本地开发环境Sail入门
ChatGPT 4.0官网入口地址 ChatGPT在线体验官网
高端建站如何打造兼具美学与转化的品牌官网?
百度浏览器如何管理插件 百度浏览器插件管理方法
Laravel观察者模式如何使用_Laravel Model Observer配置
php结合redis实现高并发下的抢购、秒杀功能的实例
如何在万网自助建站平台快速创建网站?
音响网站制作视频教程,隆霸音响官方网站?
黑客如何利用漏洞与弱口令入侵网站服务器?
Laravel怎么在Blade中安全地输出原始HTML内容
如何用美橙互联一键搭建多站合一网站?
如何用景安虚拟主机手机版绑定域名建站?
详解免费开源的DotNet二维码操作组件ThoughtWorks.QRCode(.NET组件介绍之四)
微信小程序 input输入框控件详解及实例(多种示例)
laravel怎么通过契约(Contracts)编程_laravel契约(Contracts)编程方法
Laravel怎么实现模型属性的自动加密
Windows10如何更改计算机工作组_Win10系统属性修改Workgroup
EditPlus 正则表达式 实战(3)
如何用AI帮你把自己的生活经历写成一个有趣的故事?
Laravel如何使用Livewire构建动态组件?(入门代码)
大连 网站制作,大连天途有线官网?
Laravel如何使用Gate和Policy进行权限控制_Laravel权限判定与策略规则配置
如何正确下载安装西数主机建站助手?
Laravel辅助函数有哪些_Laravel Helpers常用助手函数大全
HTML5空格和nbsp有啥关系_nbsp的作用及使用场景【说明】
javascript中对象的定义、使用以及对象和原型链操作小结
Python企业级消息系统教程_KafkaRabbitMQ高并发应用
高防服务器租用如何选择配置与防御等级?

