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

如何测试Futures的whenComplete方法内MetricsUtil交互逻辑

测试RedisWriter的Metrics交互逻辑(不手动触发Future)

原业务代码

@Slf4j
@AllArgsConstructor
public class redisWriter {

    /**
     * The connection used to connect to Elasticache.
     */
    private final StatefulRedisConnection<byte[], byte[]> elasticacheConnection;

    /**
     * Metric Util for publishing custom metrics.
     */
    private MetricsUtil metricsUtil;

    public void putRecord(final byte[] key, final byte[] value, final int ttlSeconds) {
        final Duration ttlDuration = Duration.ofSeconds(ttlSeconds);
        final SetArgs ttlSetArgs = SetArgs.Builder.ex(ttlDuration);
        final long startTime = System.currentTimeMillis();

        this.elasticacheConnection
                .async()
                .set(key, value, ttlSetArgs)
                .whenComplete((msg, exception) -> {
                    this.metricsUtil.elasticacheInteractionTime("PutRecord", startTime);
                    if (exception != null) {
                        if (exception instanceof RedisCommandTimeoutException) {
                            this.metricsUtil.elasticacheTimeout();
                        } else if (exception instanceof RedisException) {
                            log.error("Something went wrong putting in a record", exception);
                            this.metricsUtil.elasticacheError("PutRecord");
                        }
                    }
                });
    }
}

问题

需要验证metricsUtil的交互逻辑(错误处理、指标上报是否正确),但限制条件是:不能返回Future并手动触发完成,仅能调用putRecord让Future自行完成。请问用Mockito能否实现该测试,还是需要进行代码重构?

解答

可以用Mockito实现测试,不需要重构现有代码。核心思路是模拟Lettuce异步API返回的RedisFuture(本质是CompletableFuture),让它处于已完成状态(成功或失败),这样调用putRecord后,whenComplete回调会自动执行,无需手动触发。

具体测试实现(JUnit 5 + Mockito)

@ExtendWith(MockitoExtension.class)
class RedisWriterTest {

    @Mock
    private StatefulRedisConnection<byte[], byte[]> elasticacheConnection;

    @Mock
    private RedisAsyncCommands<byte[], byte[]> redisAsyncCommands;

    @Mock
    private MetricsUtil metricsUtil;

    @InjectMocks
    private RedisWriter redisWriter; // 建议将原类名改为RedisWriter,符合Java命名规范

    // 测试成功场景:上报交互时间,无错误指标
    @Test
    void putRecord_success_recordsMetrics() {
        // 模拟异步命令返回成功的Future
        when(elasticacheConnection.async()).thenReturn(redisAsyncCommands);
        when(redisAsyncCommands.set(any(byte[].class), any(byte[].class), any(SetArgs.class)))
                .thenReturn(CompletableFuture.completedFuture("OK"));

        // 执行目标方法
        redisWriter.putRecord("test-key".getBytes(), "test-value".getBytes(), 60);

        // 验证指标上报逻辑
        verify(metricsUtil).elasticacheInteractionTime(eq("PutRecord"), anyLong());
        verify(metricsUtil, never()).elasticacheTimeout();
        verify(metricsUtil, never()).elasticacheError(anyString());
    }

    // 测试超时场景:上报交互时间+超时指标
    @Test
    void putRecord_timeout_recordsTimeoutMetric() {
        // 模拟异步命令返回超时异常的Future
        when(elasticacheConnection.async()).thenReturn(redisAsyncCommands);
        RedisCommandTimeoutException timeoutEx = new RedisCommandTimeoutException("test timeout");
        when(redisAsyncCommands.set(any(byte[].class), any(byte[].class), any(SetArgs.class)))
                .thenReturn(CompletableFuture.failedFuture(timeoutEx));

        redisWriter.putRecord("test-key".getBytes(), "test-value".getBytes(), 60);

        verify(metricsUtil).elasticacheInteractionTime(eq("PutRecord"), anyLong());
        verify(metricsUtil).elasticacheTimeout();
        verify(metricsUtil, never()).elasticacheError(anyString());
    }

    // 测试普通Redis异常场景:上报交互时间+错误指标
    @Test
    void putRecord_redisException_recordsErrorMetric() {
        // 模拟异步命令返回普通Redis异常的Future
        when(elasticacheConnection.async()).thenReturn(redisAsyncCommands);
        RedisException redisEx = new RedisException("test error");
        when(redisAsyncCommands.set(any(byte[].class), any(byte[].class), any(SetArgs.class)))
                .thenReturn(CompletableFuture.failedFuture(redisEx));

        redisWriter.putRecord("test-key".getBytes(), "test-value".getBytes(), 60);

        verify(metricsUtil).elasticacheInteractionTime(eq("PutRecord"), anyLong());
        verify(metricsUtil, never()).elasticacheTimeout();
        verify(metricsUtil).elasticacheError(eq("PutRecord"));
    }
}

关键说明

  1. 模拟已完成的Future:通过让mock的set方法返回CompletableFuture.completedFuture(成功)或CompletableFuture.failedFuture(失败),确保whenComplete回调在调用putRecord后立即自动执行,满足“不手动触发Future”的限制。
  2. 覆盖所有场景:测试用例覆盖了成功、超时、普通Redis异常三种情况,验证metricsUtil的所有交互逻辑。
  3. 无需代码改动:现有业务代码的逻辑完全适配这种测试方式,不需要重构。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 00:11:02