如何测试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")); } }
关键说明
- 模拟已完成的Future:通过让mock的
set方法返回CompletableFuture.completedFuture(成功)或CompletableFuture.failedFuture(失败),确保whenComplete回调在调用putRecord后立即自动执行,满足“不手动触发Future”的限制。 - 覆盖所有场景:测试用例覆盖了成功、超时、普通Redis异常三种情况,验证
metricsUtil的所有交互逻辑。 - 无需代码改动:现有业务代码的逻辑完全适配这种测试方式,不需要重构。
内容的提问来源于stack exchange,提问作者Mylies
相关产品推荐
相关产品推荐

