如何在 Quarkus Mutiny 中断言 Uni 抛出指定异常

本文介绍在基于 mutiny 的 quarkus 项目中,如何正确编写单元测试来断言 `uni` 流在执行过程中抛出特定类型和消息的业务异常(如 `businessexception`),重点解决 `assertsubscribe

r` 类型推导错误及推荐的 `uniassertsubscriber` 用法。

在使用 SmallRye Mutiny 进行响应式编程时,Uni 是处理单个异步结果的核心类型。当业务逻辑需在数据缺失时主动抛出异常(如 BusinessException),测试该异常是否被正确触发就变得至关重要。但直接复用 Multi 的 AssertSubscriber 会导致编译错误——因为 Uni 和 Multi 的订阅器接口不兼容:UniSubscriber 与 MultiSubscriber 类型体系分离,Java 泛型无法自动推导 withSubscriber(...) 中的 S 类型边界。

✅ 正确做法是使用专为 Uni 设计的 UniAssertSubscriber(位于 io.smallrye.mutiny.test 包中),它实现了 UniSubscriber 接口,并提供链式断言方法:

@Test
public void shouldThrowAnErrorWhenUserIsNotFound() {
    // 模拟仓库返回 nullItem,触发 map 中的异常分支
    Mockito.when(userRepository.findById(Mockito.any()))
           .thenReturn(Uni.createFrom().nullItem());

    service.searchUser(new ObjectId())
           .subscribe()
           .withSubscriber(UniAssertSubscriber.create()) // ✅ 正确的 Subscriber 类型
           .assertFailedWith(BusinessException.class, "Could not find an user with the given id.");
}

⚠️ 注意事项:

  • 确保引入了正确的依赖(Quarkus 测试默认包含 mutiny-test):
    
        io.smallrye.reactive
        mutiny-test
        test
    
  • UniAssertSubscriber.create() 返回的是 UniAssertSubscriber,支持泛型推导,无需显式声明类型;
  • assertFailedWith(Class, String) 同时校验异常类型与消息内容,二者必须完全匹配(区分大小写与空格);
  • 若只需校验异常类型而忽略消息,可使用 assertFailedWith(BusinessException.class);
  • 切勿混用 AssertSubscriber(用于 Multi)和 UniAssertSubscriber(仅用于 Uni),否则将触发编译期类型错误。

? 小结:Mutiny 的测试工具链严格区分流类型。对 Uni,始终选用 UniAssertSubscriber;其链式 API 简洁可靠,是验证异常路径最直接、最符合 Mutiny 最佳实践的方式。