如何测试订阅 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
  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 是通过依赖注入或构造器注入的可替换实例。

✅ 推荐方案二:重构为可组合接口(长期最佳实践)

根本解法是将副作用分离并返回可测试的信号。修改 execute() 使其返回 Uni,而非 void:

public Uni execute() {
    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高并发应用  高防服务器租用如何选择配置与防御等级?