Testing Kafka Applications
1. Using EmbeddedKafka
EmbeddedKafka is a testing utility provided by the spring-kafka-test module that allows developers to run a Kafka broker instance embedded within their test JVM. This is particularly useful for integration testing without the need to set up and manage external Kafka clusters.
| Feature | Description | Usage |
|---|---|---|
| EmbeddedKafkaRule | JUnit rule that starts and stops an embedded Kafka broker. | @Rule annotation in JUnit tests. |
| EmbeddedKafkaExtension | JUnit 5 extension for managing the lifecycle of an embedded Kafka broker. | @ExtendWith(EmbeddedKafkaExtension.class) |
| KafkaEmbedded | Class that encapsulates the embedded Kafka broker instance. | Used to configure and manage Kafka server properties. |
Example: Using EmbeddedKafka with JUnit 4
@Rule
public EmbeddedKafkaRule embeddedKafka = new EmbeddedKafkaRule(1, 1, "test-topic");
@Test
public void testProducer() {
// Use embedded Kafka for testing
}
Example: Using EmbeddedKafka with JUnit 5
@ExtendWith(EmbeddedKafkaExtension.class)
public class KafkaIntegrationTest {
@Autowired
private EmbeddedKafka kafka;
@Test
public void testConsumer() {
// Use embedded Kafka for testing
}
}
Note: Ensure that
spring-kafka-test is included in your test dependencies.
Warning: Embedded Kafka is not suitable for performance or load testing due to its in-memory nature.
2. Creating Test Producer
| Property | Value | Description |
|---|---|---|
| bootstrap.servers | localhost:9092 |
Initial list of brokers to connect to |
| key.serializer | org.apache.kafka.common.serialization.StringSerializer |
Serializer for message keys |
| value.serializer | org.apache.kafka.common.serialization.StringSerializer |
Serializer for message values |
| acks | 1 |
Number of acknowledgments required from the leader |
Example: Basic Test Producer Setup
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("acks", "1");
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
Example: Sending a Test Message
ProducerRecord<String, String> record = new ProducerRecord<>("test-topic", "key1", "Hello Kafka");
producer.send(record);
3. Creating Test Consumer
| Property | Description | Default Value | Notes |
|---|---|---|---|
| bootstrap.servers | Comma-separated list of broker addresses | localhost:9092 | Required for connecting to Kafka cluster |
| group.id | Consumer group identifier | None | Must be set for consumer groups |
| key.deserializer | Deserializer for message keys | org.apache.kafka.common.serialization.StringDeserializer | Must match key type in topic |
| value.deserializer | Deserializer for message values | org.apache.kafka.common.serialization.StringDeserializer | Must match value type in topic |
| enable.auto.commit | Enables automatic offset commits | true | Set to false for manual control |
| auto.offset.reset | Behavior when no initial offset is found | latest | earliest, latest, none |
Example: Basic Test Consumer Setup
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test-consumer-group");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("enable.auto.commit", "false");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("test-topic"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));
for (ConsumerRecord<String, String> record : records) {
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}
}
Note: For testing, use
enable.auto.commit=false to have full control over offset commits.
Warning: Ensure
key.deserializer and value.deserializer match the actual data format in your topics.
4. Testing Streams Topology
Example: Basic Kafka Streams Test
import org.apache.kafka.streams.KafkaStreams;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.kstream.KStream;
import org.apache.kafka.streams.TestInputTopic;
import org.apache.kafka.streams.TestOutputTopic;
import org.apache.kafka.streams.state.KeyValueStore;
import org.apache.kafka.streams.state.Stores;
public class StreamsTest {
public static void main(String[] args) {
StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> source = builder.stream("input-topic");
KStream<String, String> processed = source.mapValues(value -> value.toUpperCase());
processed.to("output-topic");
KafkaStreams streams = new KafkaStreams(builder.build(), getConfiguration());
streams.start();
TestInputTopic<String, String> inputTopic = streams.testInput("input-topic");
TestOutputTopic<String, String> outputTopic = streams.testOutput("output-topic");
inputTopic.pipeInput("key1", "hello");
inputTopic.pipeInput("key2", "world");
assertEquals(outputTopic.readValues(), Arrays.asList("HELLO", "WORLD"));
}
}
| Component | Description | Usage |
|---|---|---|
| KafkaStreams | Core class for running Kafka Streams applications | Used to configure and start the streams application in tests |
| StreamsBuilder | Builds the Kafka Streams topology | Used to define the stream processing logic in tests |
| TestInputTopic | Simulates input data for testing | Used to send test data into the streams application |
| TestOutputTopic | Collects output data for assertions | Used to read and verify the results of stream processing |
Note: Ensure that the test environment has Kafka and ZooKeeper running, or use embedded versions for local testing.
Warning: Avoid using
KafkaStreams in production with test configurations. Use separate configurations for testing and production.Key Concepts
- StreamsBuilder: Used to construct the Kafka Streams topology.
- TestInputTopic: Simulates input data for testing.
- TestOutputTopic: Collects and verifies output data from streams.
5. Mocking External Dependencies
Example: Using Mockito to Mock Kafka Producer
import org.apache.kafka.clients.producer.KafkaProducer;
import org.mockito.Mockito;
// Create a mock KafkaProducer
KafkaProducer<String, String> mockProducer = Mockito.mock(KafkaProducer.class);
// Configure behavior for send
Mockito.doReturn(new org.apache.kafka.clients.producer.RecordMetadata("topic", 0, 0, 0L, 0L))
.when(mockProducer).send(Mockito.any(), Mockito.any());
// Use in test
mockProducer.send(new ProducerRecord<>("test-topic", "key", "value"));
| Feature | Description | Notes |
|---|---|---|
| Mockito | org.mockito.Mockito |
Popular mocking framework for Java |
| KafkaProducer | org.apache.kafka.clients.producer.KafkaProducer |
Mockable Kafka producer class |
| KafkaConsumer | org.apache.kafka.clients.consumer.KafkaConsumer |
Mockable Kafka consumer class |
Note: When mocking Kafka, ensure that your test environment does not depend on actual broker connectivity.
Warning: Mocking Kafka producers and consumers can mask real issues in serialization or network configuration.
Best Practices for Mocking Kafka
- Use
Mockitoor similar frameworks to simulate Kafka behavior. - Verify that messages are sent correctly without hitting the actual broker.
- Test error handling by simulating producer failures.
6. Testing Serialization
| Property | Value | Description |
|---|---|---|
| serializer.class | org.apache.kafka.common.serialization.StringSerializer |
Serializes string values to bytes |
| serializer.class | org.apache.kafka.common.serialization.ByteArraySerializer |
Serializes byte arrays directly |
| serializer.class | org.apache.kafka.common.serialization.IntegerSerializer |
Serializes integer values to bytes |
| serializer.class | org.apache.kafka.common.serialization.LongSerializer |
Serializes long values to bytes |
| serializer.class | org.apache.kafka.common.serialization.DoubleSerializer |
Serializes double values to bytes |
| serializer.class | org.apache.kafka.common.serialization.BooleanSerializer |
Serializes boolean values to bytes |
| serializer.class | org.apache.kafka.common.serialization.FloatSerializer |
Serializes float values to bytes |
| serializer.class | org.apache.kafka.common.serialization.ShortSerializer |
Serializes short values to bytes |
| serializer.class | org.apache.kafka.common.serialization.ByteSerializer |
Serializes byte values to bytes |
Example: String Serialization
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
producer.send(new ProducerRecord<String, String>("test-topic", "key", "value"));
producer.close();
Example: JSON Serialization
public class JsonSerializer<T> implements Serializer<T> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public byte[] serialize(String topic, T data) {
try {
return objectMapper.writeValueAsBytes(data);
} catch (Exception e) {
throw new SerializationException("Error serializing JSON", e);
}
}
}
Note: When testing, ensure that your test producers and consumers use the same serializers as in production to avoid serialization mismatches.
7. Testing Error Scenarios
| Error Type | Description | Testing Approach | Tools/Methods |
|---|---|---|---|
| Producer Failures | Simulate producer errors like network issues or timeouts | Use mock producers to inject failures during message sending | MockProducer, TestUtils |
| Consumer Failures | Simulate consumer errors such as message processing failures or rebalancing issues | Inject exceptions in consumer logic to test error recovery and retry mechanisms | ConsumerInterceptor, MockConsumer |
| Broker Failures | Simulate broker unavailability or partition leader changes | Use embedded Kafka or Docker containers to simulate broker downtime | KafkaServer, docker-compose |
| Network Errors | Simulate network partitions or latency issues between clients and brokers | Use network simulation tools like tc or netem |
NetworkPartitionSimulator, tc |
Example: Simulating Producer Failures
import org.apache.kafka.clients.producer.MockProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringSerializer;
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
MockProducer<String, String> mockProducer = new MockProducer<>(true, new StringSerializer(), new StringSerializer());
// Simulate a send failure
mockProducer.sendError(new RuntimeException("Network timeout"));
Example: Testing Consumer Retry Logic
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;
Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "test-group");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
// Simulate processing failure
try {
consumer.subscribe(Collections.singletonList("test-topic"));
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));
for (ConsumerRecord<String, String> record : records) {
throw new RuntimeException("Processing failed");
}
} catch (Exception e) {
// Retry logic or error handling
System.out.println("Retrying after failure: " + e.getMessage());
}
Note: When simulating errors, ensure that your tests cover both transient and permanent failures to validate robustness.
8. Integration Testing
Example: Integration Test Setup with Embedded Kafka
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class KafkaIntegrationTest {
private static final String BOOTSTRAP_SERVERS = "localhost:9092";
private static final String TOPIC_NAME = "test-topic";
public void testProducerAndConsumer() {
Properties producerProps = new Properties();
producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, BOOTSTRAP_SERVERS);
producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
KafkaProducer<String, String> producer = new KafkaProducer<>(producerProps);
ProducerRecord<String, String> record = new ProducerRecord<>(TOPIC_NAME, "key1", "message1");
producer.send(record);
producer.flush();
producer.close();
}
}
| Component | Description | Notes |
|---|---|---|
| Kafka Embedded | Embedded Kafka server for testing | Useful for unit and integration tests |
| TestContainers | Containerized Kafka for integration tests | Provides real Kafka instances in containers |
| Kafka Streams Test Utilities | Utilities for testing Kafka Streams applications | Part of Kafka's test infrastructure |
Note: Integration tests often involve setting up a Kafka cluster, producing messages, and verifying consumption or processing.
Best Practices for Integration Testing
- Use embedded Kafka or TestContainers to simulate real environments
- Ensure test isolation by using unique topic names per test
- Validate message order and content in consumer tests
- Test error handling and recovery scenarios
9. Performance Testing
| Aspect | Description | Tools |
|---|---|---|
| Throughput | Measures the number of messages processed per second | Kafka Load Test, JMH |
| Latency | Time taken for a message to be produced and consumed | Kafka Producer/Consumer, JMeter |
| Resource Utilization | Monitoring CPU, Memory, Disk I/O | Systemd, Prometheus, Grafana |
Example: Measuring Throughput
kafka-producer-perf-test.sh \
--topic test-topic \
--num-records 1000000 \
--record-size 1024 \
--throughput 10000 \
--producer.config producer.properties
Example: Latency Measurement
kafka-consumer-perf-test.sh \
--topic test-topic \
--messages 1000000 \
--consumer.config consumer.properties
Note: Use
kafka-producer-perf-test.sh and kafka-consumer-perf-test.sh for basic performance testing. For more complex scenarios, consider using JMeter or custom load testing frameworks.
10. Load Testing
Example: Kafka Load Test Configuration
name: kafka-load-test
topics:
- name: test-topic
partitions: 10
replication-factor: 3
producer:
throughput: 10000
message-size: 1024
consumer:
concurrency: 5
batch-size: 100
duration: 3600s
| Configuration | Description | Default Value | Notes |
|---|---|---|---|
| name | Test scenario name | default-test |
Used for reporting and logging |
| topics | List of topics to create | [] |
Each topic has name, partitions, replication-factor |
| producer.throughput | Messages per second to produce | 1000 |
Adjust based on system capacity |
| producer.message-size | Size of each message in bytes | 1024 |
Simulates realistic payload sizes |
| consumer.concurrency | Number of concurrent consumers | 1 |
Higher values increase throughput |
| duration | Duration of the test in seconds | 300s |
Use ISO 8601 format (e.g., 1h, 30m) |
Note: Use
kafka-producer-perf-test.sh and kafka-consumer-perf-test.sh for built-in load testing tools.
Warning: Ensure sufficient disk space and memory for large-scale load tests to avoid system instability.