KafkaTemplate
Mocking Data
Unit Testing
Software Development
Kafka Testing

How to mock result from KafkaTemplate

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

When unit-testing code that uses Spring Kafka's KafkaTemplate, the goal is usually to test your service logic, not Kafka itself. In most cases, that means mocking the result of send and verifying how your code reacts to success, failure, or returned metadata.

The practical detail is that modern KafkaTemplate.send methods return a CompletableFuture<SendResult<K, V>>. So the mock needs to simulate the future behavior your production code actually depends on.

Start With the Code Under Test

Suppose your service sends a message and returns the partition from the broker metadata:

java
1import java.util.concurrent.CompletableFuture;
2import org.springframework.kafka.core.KafkaTemplate;
3import org.springframework.kafka.support.SendResult;
4
5public class NotificationService {
6    private final KafkaTemplate<String, String> kafkaTemplate;
7
8    public NotificationService(KafkaTemplate<String, String> kafkaTemplate) {
9        this.kafkaTemplate = kafkaTemplate;
10    }
11
12    public int sendNotification(String message) throws Exception {
13        CompletableFuture<SendResult<String, String>> future =
14            kafkaTemplate.send("notifications", message);
15
16        SendResult<String, String> result = future.get();
17        return result.getRecordMetadata().partition();
18    }
19}

This method does not care about Kafka internals. It cares about the completed future and the metadata inside SendResult. That is exactly what the unit test should control.

Mock a Successful Send

For the success path, return a completed future containing a real SendResult:

java
1import static org.junit.jupiter.api.Assertions.assertEquals;
2import static org.mockito.Mockito.mock;
3import static org.mockito.Mockito.when;
4
5import java.util.concurrent.CompletableFuture;
6import org.apache.kafka.clients.producer.ProducerRecord;
7import org.apache.kafka.clients.producer.RecordMetadata;
8import org.apache.kafka.common.TopicPartition;
9import org.junit.jupiter.api.Test;
10import org.springframework.kafka.core.KafkaTemplate;
11import org.springframework.kafka.support.SendResult;
12
13class NotificationServiceTest {
14
15    @Test
16    void returnsPartitionOnSuccess() throws Exception {
17        KafkaTemplate<String, String> template = mock(KafkaTemplate.class);
18
19        ProducerRecord<String, String> record =
20            new ProducerRecord<>("notifications", "hello");
21        RecordMetadata metadata = new RecordMetadata(
22            new TopicPartition("notifications", 2),
23            0,
24            5,
25            123L,
26            0,
27            0
28        );
29        SendResult<String, String> sendResult = new SendResult<>(record, metadata);
30
31        CompletableFuture<SendResult<String, String>> future =
32            CompletableFuture.completedFuture(sendResult);
33
34        when(template.send("notifications", "hello")).thenReturn(future);
35
36        NotificationService service = new NotificationService(template);
37        assertEquals(2, service.sendNotification("hello"));
38    }
39}

This test is fast, isolated, and expressive. It verifies your service behavior without needing a broker or embedded Kafka setup.

Mock a Failed Send

If the code under test handles exceptions, return a future that completes exceptionally:

java
1import static org.junit.jupiter.api.Assertions.assertThrows;
2import static org.mockito.Mockito.mock;
3import static org.mockito.Mockito.when;
4
5import java.util.concurrent.CompletableFuture;
6import org.junit.jupiter.api.Test;
7import org.springframework.kafka.core.KafkaTemplate;
8import org.springframework.kafka.support.SendResult;
9
10class FailureTest {
11
12    @Test
13    void throwsWhenSendFails() {
14        KafkaTemplate<String, String> template = mock(KafkaTemplate.class);
15
16        CompletableFuture<SendResult<String, String>> future = new CompletableFuture<>();
17        future.completeExceptionally(new RuntimeException("broker unavailable"));
18
19        when(template.send("notifications", "hello")).thenReturn(future);
20
21        NotificationService service = new NotificationService(template);
22
23        assertThrows(Exception.class, () -> service.sendNotification("hello"));
24    }
25}

That pattern is useful for retry logic, exception mapping, metrics, or dead-letter decisions.

When Verification Alone Is Enough

Some code sends a message and does not inspect the result. In that case, you may only need to verify the interaction:

java
1import static org.mockito.Mockito.mock;
2import static org.mockito.Mockito.times;
3import static org.mockito.Mockito.verify;
4
5KafkaTemplate<String, String> template = mock(KafkaTemplate.class);
6
7template.send("notifications", "hello");
8
9verify(template, times(1)).send("notifications", "hello");

Do not overbuild a fake SendResult if the production code never reads it. The mock should be as small as the behavior under test.

Match the Mock to Your Spring Kafka Version

Older examples on the internet may show ListenableFuture because older Spring Kafka versions used that type. Newer versions use CompletableFuture. The test pattern is the same, but the mocked type must match the API your project actually compiles against.

That is an easy place to get confused when copying snippets from older blog posts or Stack Overflow answers.

Unit Test Versus Integration Test

Mocking KafkaTemplate is for unit tests. If you need to verify serializer configuration, topic setup, or actual broker interaction, that is an integration-test concern. Mixing the two styles usually makes tests slower and harder to maintain.

A good rule is:

  • unit tests mock the future and verify service logic
  • integration tests use real Kafka infrastructure when the transport behavior itself matters

Common Pitfalls

The biggest mistake is mocking only the method call and forgetting that the code under test depends on the returned future. If your service awaits the future, the test must control that result explicitly.

Another common issue is using outdated examples based on ListenableFuture in a project that now expects CompletableFuture. Always match the mock type to the version you are running.

It is also easy to overcomplicate a unit test by starting a real broker when the only behavior under test is local exception handling or metadata inspection. Use integration tests only when Kafka itself is the thing being exercised.

Finally, keep the mock aligned with the actual code path. If the service ignores metadata, do not build metadata-heavy fixtures just because the API allows it.

Summary

  • Mock KafkaTemplate.send at the future boundary your code actually uses.
  • Use CompletableFuture.completedFuture for success cases in modern Spring Kafka code.
  • Use an exceptionally completed future to test failure handling.
  • Verify only the interaction when the send result is ignored.
  • Distinguish unit tests that mock KafkaTemplate from integration tests that need a real broker.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.