Microservices & Kafka Testing

51Microservices & Event-Driven (Kafka) Testing

What you will master here

  • Microservices testing challenges (chained calls, async events)
  • Kafka basics: topics, partitions, producers, consumers, offsets
  • Schema registry + Avro/Protobuf evolution
  • Producing/consuming from tests
  • Async assertion patterns (poll, await event)
  • Idempotency, retries, dead-letter queues
  • Testing strategies: contract + integration + chaos

51.1 Microservices testing pyramid

  1. Unit — within one service
  2. Component — service in isolation with real DB, mocked deps
  3. Contract — Pact / Spring Cloud Contract — cross-service expectations (Module 49)
  4. Integration — small graph of services together
  5. End-to-end — full system, minimal
  6. Synthetic monitoring — prod heartbeat (Module 30)

51.2 Kafka in 5 minutes

51.3 Producing + consuming from tests (Node)

import { Kafka } from 'kafkajs';
const kafka = new Kafka({ clientId: 'test', brokers: ['localhost:9092'] });

test('publishing an order triggers an email', async () => {
  // Produce
  const producer = kafka.producer();
  await producer.connect();
  await producer.send({
    topic: 'orders.created',
    messages: [{ key: 'ord-123', value: JSON.stringify({ id: 'ord-123', total: 99 }) }],
  });
  await producer.disconnect();

  // Verify side effect via DB / downstream API / consume another topic
  const consumer = kafka.consumer({ groupId: 'test-' + Date.now() });
  await consumer.connect();
  await consumer.subscribe({ topic: 'emails.sent', fromBeginning: false });
  let received: any = null;
  await consumer.run({ eachMessage: async ({ message }) => {
    const v = JSON.parse(message.value!.toString());
    if (v.orderId === 'ord-123') received = v;
  }});
  await expect.poll(() => received, { timeout: 15_000 }).not.toBeNull();
  expect(received.subject).toContain('Your order ord-123');
  await consumer.disconnect();
});

51.4 Schema registry + Avro/Protobuf

Producers and consumers agree on a binary schema; the registry stores versions. Why it matters for tests:

51.5 Async assertion patterns

// expect.poll — retry an async check
await expect.poll(async () => {
  const r = await db.query("SELECT status FROM orders WHERE id = $1", ['ord-1']);
  return r.rows[0]?.status;
}, { timeout: 10_000, intervals: [200, 500, 1000] }).toBe('paid');

// Awaitable event helper
async function awaitMessage(topic: string, match: (v: any) => boolean, timeoutMs = 10_000) {
  return new Promise((resolve, reject) => {
    const consumer = kafka.consumer({ groupId: `t-${Date.now()}` });
    const timer = setTimeout(() => reject(new Error('timeout')), timeoutMs);
    (async () => {
      await consumer.connect();
      await consumer.subscribe({ topic, fromBeginning: false });
      await consumer.run({ eachMessage: async ({ message }) => {
        const v = JSON.parse(message.value!.toString());
        if (match(v)) { clearTimeout(timer); await consumer.disconnect(); resolve(v); }
      }});
    })();
  });
}

51.6 Idempotency & retries

51.7 Eventual consistency

In an event-driven system the DB write happens, then events propagate to downstream services and update their projections. Tests must wait for that propagation — never sleep for a fixed time; poll on the observable end state.

51.8 Tools

ToolPurpose
Testcontainers KafkaSpin a real Kafka per test suite
kafkajs / Spring Kafka / confluent-kafka-pythonClient libs
kcat (formerly kafkacat)CLI producer/consumer
Conduktor / Kafdrop / AKHQUI to browse topics
Pact MessageContract testing for event payloads

51.9 Service mesh / observability

51.10 Chaos engineering for microservices

Module 56 — Microservices/Kafka Q&A

What's the biggest testing difference between monolith and microservices?
Communication paths. Monolith: in-process calls. Microservices: network + async events. Each call can fail or be slow; events arrive out of order. Tests must cover failure modes and eventual consistency, not just happy paths.
Why is contract testing important here?
Without it, a producer can break consumers silently. End-to-end tests catch it eventually but expensively. Contracts pin expectations cheaply per service — producer CI fails fast.
What's a Kafka topic and partition?
Topic = named append-only log. Partition = a shard of the topic, ordered internally. More partitions = more parallel consumer throughput, but order is only guaranteed within a partition.
What's at-least-once delivery and how do you test for it?
Kafka guarantees a message is delivered AT LEAST once — but possibly multiple times. Handlers must be idempotent. Test: send the same message twice (same key), assert side effect happens only once.
What's a consumer group?
A set of consumers cooperatively reading one topic. Kafka assigns partitions to group members; each partition has exactly one consumer in the group. Adding consumers up to partition count scales horizontally.
How do you assert an async event-driven side effect?
Don't sleep. Use a polling helper (expect.poll) that re-queries the observable state until matching or timeout. Or subscribe to the downstream topic/DB change and resolve a Promise on first match.
What is a dead-letter queue?
Topic where poison messages land after retry budget exhausts. Lets the main pipeline keep flowing while humans triage failures. Test the DLQ end of the pipeline too — verify a known-bad message lands there.
How do you test schema evolution safely?
Use a schema registry with compatibility rules (BACKWARD/FORWARD/FULL). CI runs producer test against old consumer; consumer test against old producer payload. Add a "schema diff" gate that fails on incompatible changes.
How do you trace a request across services?
Distributed tracing (OpenTelemetry, Jaeger). Each entry point creates a trace id; downstream calls propagate it via headers. Tests can fetch the trace and assert the call graph + per-span latency.
How would you chaos-test a microservices stack?
Inject failures with Chaos Mesh / Litmus on a staging cluster: kill pods, add latency, drop messages. Run synthetic flows; assert the system stays usable (retries kick in, circuit breaker opens, DLQ catches the rest). Done in scheduled game days, not on every PR.