Spring Kafka ProducerRecord Mock测试返回null future问题及解决方案
问题根本原因
这是Mockito参数匹配的默认规则导致的:
- Mockito对方法打桩时,默认使用
equals()方法判断入参与打桩时指定的参数是否匹配,只有匹配成功才会返回预设的返回值。 - 你测试代码里打桩时传入的是被
@Mock注解生成的ProducerRecord代理对象,而业务代码实际调用kafkaTemplate.send()时传入的是业务侧自行new出来的真实ProducerRecord实例:- 两个对象本身不是同一个实例
- Mock生成的
ProducerRecord的topic、key、value等核心属性都是默认null,和业务构造的实例属性完全不一致,equals()判断返回false
- 匹配失败时Mockito就会返回类型默认值,
ListenableFuture是引用类型,所以返回null。
解决方案合理性与最佳实践建议
你使用any(ProducerRecord.class)的方案是可行的,但要分场景判断是否符合最佳实践:
适用场景
如果你的单元测试只需要验证kafka发送动作是否触发,不需要校验发送的消息内容,使用any(ProducerRecord.class)是完全合理的,属于简化测试的常用操作,符合最佳实践。
对应打桩代码:
when(producer.send(any(ProducerRecord.class))).thenReturn(future);
需调整的场景
如果你的测试需要校验发送的消息的topic、key、value是否符合业务预期,any匹配规则过于宽松,无法覆盖参数校验的测试需求,这时候推荐两种更严谨的方案:
方案1:使用属性匹配器
针对你关心的属性做精准匹配,示例:
when(producer.send(argThat(record -> Objects.equals(record.topic(), "expectedTopic") && Objects.equals(record.key(), expectedKey) && Objects.equals(record.value(), expectedValue) ))).thenReturn(future);
方案2:使用ArgumentCaptor捕获参数后断言
先打桩用any匹配,执行完业务逻辑后捕获发送的ProducerRecord做断言:
// 打桩 when(producer.send(any(ProducerRecord.class))).thenReturn(future); // 执行业务代码 businessService.sendMsg(); // 捕获参数做断言 ArgumentCaptor<ProducerRecord> captor = ArgumentCaptor.forClass(ProducerRecord.class); verify(producer).send(captor.capture()); ProducerRecord actualRecord = captor.getValue(); assertEquals("expectedTopic", actualRecord.topic()); assertEquals(expectedKey, actualRecord.key()); assertEquals(expectedValue, actualRecord.value());
内容的提问来源于stack exchange,提问作者harkesh kumar
相关产品推荐
相关产品推荐

