You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何测试Quarkus中基于Mutiny Uni实现重试的方法?

问题描述

我正在使用Quarkus、RESTEasy和Mutiny框架,编写了一个调用REST客户端并在失败时重试的方法:

class MyService {
  private MyRestClient restClient;

  public Uni<Response> fetchData() {
        return restClient.fetchData()
                .onFailure().retry().withBackOff(Duration.ofMillis(100), Duration.ofSeconds(5))
                .atMost(5);
    }
}

为验证重试逻辑是否正确,编写了如下测试代码:

@Test
public void shouldRetry() { 
    Mockito.when(restClient.fetchData())
        .thenReturn(
            Uni.createFrom().failure(new RuntimeException("Failed to fetch data")),
            Uni.createFrom().item(Response.ok().build())
        );

        assertDoesNotThrow(() -> myService.fetchData().await().indefinetely());

        Mockito.verify(restClient, Mockito.times(2)).fetchData();
    }

测试在assertDoesNotThrow断言处失败:重试逻辑虽触发,但并未调用新的fetchData方法,而是一直复用最初返回异常的同一个Uni,导致重试5次后抛出IllegalStateException: Retries exhausted: 5/5。

问题原因

Mockito的thenReturn方法会直接返回你传入的Uni实例,每次调用restClient.fetchData()都会拿到同一个对象。而Mutiny的重试逻辑是重新订阅已有的Uni实例,不会触发restClient.fetchData()方法的再次调用。第一次返回的失败Uni无论订阅多少次都会抛出异常,自然会耗尽重试次数。

修复方案

改用Mockito的thenAnswer方法,让每次调用restClient.fetchData()都生成并返回新的Uni实例,确保重试时能触发新的调用:

@Test
public void shouldRetry() {
    // 用Answer来每次调用生成新的Uni
    Mockito.when(restClient.fetchData())
            .thenAnswer(invocationOnMock -> {
                // 模拟第一次调用失败,第二次成功
                if (Mockito.mockingDetails(restClient).getInvocations().size() < 1) {
                    return Uni.createFrom().failure(new RuntimeException("Failed to fetch data"));
                } else {
                    return Uni.createFrom().item(Response.ok().build());
                }
            });

    // 修正拼写错误:indefinetely → indefinitely
    assertDoesNotThrow(() -> myService.fetchData().await().indefinitely());

    Mockito.verify(restClient, Mockito.times(2)).fetchData();
}

另外,原测试代码中的await().indefinetely()存在拼写错误,必须修正为await().indefinitely(),否则会编译报错。

补充优化

如果需要更灵活的多轮返回(比如前N次失败,第N+1次成功),可以用thenAnswer链式调用的方式,每次调用都生成新的Uni实例:

@Test
public void shouldRetry() {
    Mockito.when(restClient.fetchData())
            // 第一次调用返回失败的新Uni实例
            .thenAnswer(inv -> Uni.createFrom().failure(new RuntimeException("Failed")))
            // 第二次调用返回成功的新Uni实例
            .thenAnswer(inv -> Uni.createFrom().item(Response.ok().build()));

    assertDoesNotThrow(() -> myService.fetchData().await().indefinitely());
    Mockito.verify(restClient, Mockito.times(2)).fetchData();
}

内容的提问来源于stack exchange,提问作者Allan Juan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 13:35:10