Kafka Testing
Tests prove that a Kafka application behaves correctly before it reaches real users. Messaging code brings special challenges: asynchronous delivery, ordering rules, and external brokers. A layered testing approach tackles each challenge with the right tool. Picture building a bridge: engineers test a single bolt, then a joint, then the full span under load, and each test answers a different question.
The Testing Pyramid
/\
/ \ End-to-end tests (few, slow)
/----\
/ \ Integration tests with a real broker
/--------\
/ \ Unit tests with mock clients (many, fast)
/------------\
Write many fast unit tests, a moderate number of integration tests, and a handful of end-to-end checks.
Unit Tests with MockProducer
The Kafka client library includes MockProducer, a fake producer that records every message in memory. No broker is needed.
MockProducer<String, String> mock =
new MockProducer<>(true, new StringSerializer(), new StringSerializer());
OrderService service = new OrderService(mock);
service.placeOrder("order-1", "Laptop");
assertEquals(1, mock.history().size());
assertEquals("order-1", mock.history().get(0).key());
The first argument, true, tells the mock to complete each send automatically. Use false to simulate slow or failing sends and check how the code reacts.
Unit Tests with MockConsumer
MockConsumer<String, String> mock =
new MockConsumer<>(OffsetResetStrategy.EARLIEST);
TopicPartition tp = new TopicPartition("orders", 0);
mock.assign(List.of(tp));
mock.updateBeginningOffsets(Map.of(tp, 0L));
mock.addRecord(new ConsumerRecord<>("orders", 0, 0L, "order-1", "Laptop"));
// Pass the mock to your processing class and assert the result
The mock lets a test feed exact records into the application and verify the output.
Integration Tests with Testcontainers
Testcontainers starts a real Kafka broker inside a temporary container for each test run.
@Container
static KafkaContainer kafka =
new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.6.0"));
@Test
void sendsAndReceives() {
String servers = kafka.getBootstrapServers();
// create producer and consumer with these servers
// send a message, poll, and assert the value
}
These tests catch problems that mocks hide, such as wrong serializer settings, missing topics, and consumer group behavior. They run slower, so keep the count modest.
Testing Kafka Streams Logic
The TopologyTestDriver runs a Streams topology in memory without any broker.
TestInputTopic<String, String> input =
driver.createInputTopic("input", new StringSerializer(), new StringSerializer());
TestOutputTopic<String, String> output =
driver.createOutputTopic("output", new StringDeserializer(), new StringDeserializer());
input.pipeInput("k1", "hello");
assertEquals("HELLO", output.readValue());
What to Verify
| Area | Question to Answer |
|---|---|
| Serialization | Does the consumer read exactly what the producer wrote? |
| Keys | Do related events share a key and a partition? |
| Error paths | Does a bad message reach the dead letter topic? |
| Offsets | Does a restart resume without loss or duplicates? |
| Idempotency | Does a repeated message leave the result unchanged? |
Avoid Flaky Tests
- Never rely on fixed sleep times; poll until the expected record arrives or a timeout passes
- Give every test its own topic name and consumer group
- Delete topics after the run
- Set auto.offset.reset to earliest so the test reads data written before the subscription
Load and Failure Tests
Before launch, measure throughput with the kafka-producer-perf-test.sh tool and consumer lag under peak traffic. Stop one broker during a test run and confirm that producers and consumers recover without data loss. These checks reveal weak settings long before customers do.
Test Data Tips
Keep a small library of sample messages that cover normal cases, empty fields, very large values, and malformed content. Reuse the same samples across unit and integration tests so results stay comparable. Store the samples beside the test code, and update them whenever the message schema changes.
