测试Mock KafkaTemplate时send返回的ListenableFuture始终为null
问题分析
测试中kafkaTemplate.send()返回null的核心原因是Mock对象未正确生效,常见场景包括:
- 混用
@SpringBootTest与@Mock:@SpringBootTest会启动Spring上下文,主类依赖的KafkaTemplate来自Spring容器而非你创建的Mock实例。 @InjectMocks未完成依赖注入:主类的KafkaTemplate字段可能是final或构造函数注入逻辑不匹配,导致Mockito无法注入Mock对象。- 参数匹配不精确:
any(ProducerRecord.class)可能因泛型或参数细节未匹配到主类创建的ProducerRecord实例。
修复方案
方案1:单元测试(不启动Spring上下文)
移除@SpringBootTest,仅用Mockito管理依赖注入,避免Spring上下文干扰:
@ExtendWith(MockitoExtension.class) class QueryServiceProducerTest { @InjectMocks private QueryServiceProducer classUnderTest; @Mock KafkaTemplate<String, byte[]> template; QueryServiceRequest queryServiceRequest; QueryServiceSendStatus expectedStatus; @BeforeEach void setup() { int offset = 0; expectedStatus = QueryServiceSendStatus.builder() .offset(0L) .partition(0) .error(null) .topic("topic") .timestamp(123L) .build(); queryServiceRequest = new QueryServiceRequest(String.valueOf(UUID.randomUUID()), "data", "topic"); RecordMetadata recordMetadata = new RecordMetadata( new TopicPartition(queryServiceRequest.getTopic(), offset), 0L, 0L, 123L, 0L, 0, 0); SettableListenableFuture<SendResult<String, byte[]>> future = new SettableListenableFuture<>(); future.set(new SendResult<>( new ProducerRecord<>("topic", 0, queryServiceRequest.getId(), "data".getBytes(), null), recordMetadata)); // 用更宽松的参数匹配确保命中Mock逻辑 when(template.send(any())).thenReturn(future); } @Test void testQueryServiceProducerIsOK() throws ExecutionException, InterruptedException { QueryServiceSendStatus status = classUnderTest.sendMessage( queryServiceRequest.getTopic(), 0, queryServiceRequest.getId(), queryServiceRequest.getData().getBytes(), null); Assertions.assertEquals(expectedStatus, status); } }
- 优势:测试速度快,完全由Mockito控制依赖,避免Spring代理干扰。
- 注意:
@Async注解在此场景下不会生效(无Spring代理),方法会同步执行,不影响Mock逻辑验证。
方案2:集成测试(保留Spring上下文)
使用@MockBean替换@Mock,让Spring上下文使用你的Mock实例:
@SpringBootTest class QueryServiceProducerTest { @Autowired private QueryServiceProducer classUnderTest; @MockBean KafkaTemplate<String, byte[]> template; QueryServiceRequest queryServiceRequest; QueryServiceSendStatus expectedStatus; @BeforeEach void setup() { int offset = 0; expectedStatus = QueryServiceSendStatus.builder() .offset(0L) .partition(0) .error(null) .topic("topic") .timestamp(123L) .build(); queryServiceRequest = new QueryServiceRequest(String.valueOf(UUID.randomUUID()), "data", "topic"); RecordMetadata recordMetadata = new RecordMetadata( new TopicPartition(queryServiceRequest.getTopic(), offset), 0L, 0L, 123L, 0L, 0, 0); SettableListenableFuture<SendResult<String, byte[]>> future = new SettableListenableFuture<>(); future.set(new SendResult<>( new ProducerRecord<>("topic", 0, queryServiceRequest.getId(), "data".getBytes(), null), recordMetadata)); when(template.send(any(ProducerRecord.class))).thenReturn(future); } @Test void testQueryServiceProducerIsOK() throws ExecutionException, InterruptedException { QueryServiceSendStatus status = classUnderTest.sendMessage( queryServiceRequest.getTopic(), 0, queryServiceRequest.getId(), queryServiceRequest.getData().getBytes(), null); Assertions.assertEquals(expectedStatus, status); } }
- 优势:保留Spring上下文,
@Async注解正常生效,适合需要验证完整Spring逻辑的集成测试。
额外检查项
- 主类的
KafkaTemplate字段不能是final(构造函数注入除外),否则Mockito无法完成注入。 - 确保导入正确的Mockito类:
org.mockito.Mock、org.mockito.junit.jupiter.MockitoExtension、org.mockito.Mockito.when。 - 如果主类用构造函数注入
KafkaTemplate,需确保构造函数参数与@Mock实例类型完全匹配。
内容的提问来源于stack exchange,提问作者Beez
相关产品推荐
相关产品推荐

