Modern QA2026Kafka with Testcontainers — tiles
Log inJoin
53 / 75 · 08 Infrastructure as Code Testing · Testcontainers for Infrastructure Testing← prev⊞ allnext →☰ Read as one page

10.3Kafka with Testcontainers

Kafka is one of the most common use cases for Testcontainers. Running a real Kafka broker in your test suite eliminates the fragility of Kafka mocks.

# tests/integration/test_kafka_consumer.py
import pytest
from testcontainers.kafka import KafkaContainer
from confluent_kafka import Producer, Consumer
import json
import time

@pytest.fixture(scope="module")
def kafka():
    """Start a Kafka container for the test module."""
    with KafkaContainer("confluentinc/cp-kafka:7.6.0") as kafka:
        yield kafka

def test_order_event_processing(kafka):
    """Test that order events are correctly produced and consumed."""
    bootstrap_servers = kafka.get_bootstrap_server()

    # Produce a test event
    producer = Producer({"bootstrap.servers": bootstrap_servers})
    order_event = {
        "eventType": "OrderCreated",
        "orderId": "ORD-789",
        "timestamp": "2026-02-09T10:00:00Z",
        "payload": {"items": [{"sku": "WIDGET-A", "qty": 1}]}
    }
    producer.produce("orders", json.dumps(order_event).encode())
    producer.flush()

    # Consume and verify
    consumer = Consumer({
        "bootstrap.servers": bootstrap_servers,
        "group.id": "test-group",
        "auto.offset.reset": "earliest",
    })
    consumer.subscribe(["orders"])

    msg = consumer.poll(timeout=10.0)
    assert msg is not None
    assert msg.error() is None
    event = json.loads(msg.value())
    assert event["eventType"] == "OrderCreated"
    assert event["orderId"] == "ORD-789"

    consumer.close()

def test_consumer_handles_malformed_messages(kafka):
    """Test that the consumer gracefully handles non-JSON messages."""
    bootstrap_servers = kafka.get_bootstrap_server()

    producer = Producer({"bootstrap.servers": bootstrap_servers})
    producer.produce("orders", b"this is not json")
    producer.flush()

    consumer = Consumer({
        "bootstrap.servers": bootstrap_servers,
        "group.id": "test-malformed-group",
        "auto.offset.reset": "earliest",
    })
    consumer.subscribe(["orders"])

    msg = consumer.poll(timeout=10.0)
    assert msg is not None

    # The application's consumer should handle this without crashing
    try:
        json.loads(msg.value())
        assert False, "Should have raised ValueError"
    except (json.JSONDecodeError, ValueError):
        pass  # Expected: consumer should log and skip

    consumer.close()

def test_multiple_partitions(kafka):
    """Test that messages are distributed across partitions."""
    bootstrap_servers = kafka.get_bootstrap_server()

    # Create a topic with multiple partitions using admin client
    from confluent_kafka.admin import AdminClient, NewTopic
    admin = AdminClient({"bootstrap.servers": bootstrap_servers})
    topic = NewTopic("multi-partition", num_partitions=3, replication_factor=1)
    admin.create_topics([topic])

    # Produce messages with different keys (keys determine partition)
    producer = Producer({"bootstrap.servers": bootstrap_servers})
    for i in range(30):
        key = f"customer-{i % 3}"  # 3 different keys
        producer.produce(
            "multi-partition",
            key=key.encode(),
            value=json.dumps({"index": i}).encode(),
        )
    producer.flush()

    # Verify messages are consumable
    consumer = Consumer({
        "bootstrap.servers": bootstrap_servers,
        "group.id": "multi-partition-test",
        "auto.offset.reset": "earliest",
    })
    consumer.subscribe(["multi-partition"])

    messages = []
    deadline = time.time() + 15
    while len(messages) < 30 and time.time() < deadline:
        msg = consumer.poll(timeout=1.0)
        if msg and not msg.error():
            messages.append(json.loads(msg.value()))

    assert len(messages) == 30
    consumer.close()