FinCodeCow · Глава XV

Как проверить «ненаблюдаемое»

Полное руководство по тестированию событийно-управляемых микросервисов: от unit-тестов топологий до production testing

В мире синхронных REST API тестирование сводится к простой формуле: отправил запрос — получил ответ — проверил код состояния. Но в событийно-управляемой архитектуре (EDA) этот подход разбивается о реальность. Здесь нет прямого ответа. Событие улетает в брокер, обрабатывается асинхронно, изменяет чьё-то состояние и порождает новые события. Как убедиться, что система работает правильно, если результат её работы растянут во времени?

К счастью, микросервисная архитектура обладает высокой модульностью. Если синхронные монолиты требовали развёртывания «полотна» зависимостей для одного теста, то событийные микросервисы позволяют изолировать логику. В этой главе мы разберём многоуровневую стратегию тестирования EDA: от чистых функций до полноценных клонированных кластеров.

Это пятнадцатая глава цикла. Она опирается на главу III (schema testing, совместимость), главу V (TopologyTestDriver, state stores), главу VI (watermarks, event time), главу VII (state store testing, Changelog), главу VIII (тестирование компенсаций), главу X (BPC), главу XI (MiniCluster, TestHarness), главу XII (EmbeddedKafka), главу XIII (contract testing) и главу XIV (testing infrastructure). Здесь мы замыкаем цикл: от паттернов и архитектуры — к качеству.

• • •

IПирамида тестирования EDA

Классическая пирамида тестирования (unit → integration → E2E) в EDA имеет свою специфику. Ключевое отличие — contract testing становится отдельным слоем, параллельным пирамиде.

Уровень Доля Скорость Что проверяет
Unit ~60% Миллисекунды Функции без состояния, топология, property-based
Integration ~30% Секунды–минуты Testcontainers, rebalance, failure, state store
Contract параллельно Секунды Schema testing, Pact, snapshot testing
E2E ~10% Минуты–часы Эфемерные кластеры, chaos, load, replay
Принцип пирамиды

Чем ниже уровень — тем больше тестов и тем они быстрее. Чем выше — тем меньше тестов и тем они дороже. Для EDA это правило работает точно так же, но с одним отличием: contract testing становится отдельным слоем, потому что контракты между производителем и потребителем — критичны, но не вписываются в классическую пирамиду.

• • •

IIУровень 1: функции и топологии

Событийно-управляемая топология — это конвейер функций: filter, map, aggregate, join. Первый шаг — протестировать эти функции изолированно.

Функции без состояния

Трансформации, фильтрация и маршрутизация. Они не помнят прошлого. Тестировать тривиально: подали массив событий — проверили массив на выходе. Граничные условия, null, невалидные схемы — главные мишени.

Функции с состоянием

Агрегации и оконные вычисления. Здесь понадобится mock хранилища состояний (Key-Value store). Эмулируем память сервиса, чтобы проверить, как функция накапливает и схлопывает данные во времени.

TopologyTestDriver: тестирование топологии в памяти

Что если ошибка не в функции map, а в том, как фреймворк маршрутизирует события между groupByKey и reduce? Писать собственный эмулятор брокера — плохая идея. Используйте встроенные драйверы.

// TopologyTestDriver для Kafka Streams
TopologyTestDriver driver = new TopologyTestDriver(topology, config);

TestInputTopic<String, OrderEvent> input = driver.createInputTopic(
    "orders",
    new StringSerializer(),
    new OrderEventSerializer());

TestOutputTopic<String, Long> output = driver.createOutputTopic(
    "customer-totals",
    new StringDeserializer(),
    new LongDeserializer());

input.pipeInput("order-1", new OrderEvent("cust-42", 150.0));

assertThat(output.readValueToMap()).containsEntry("cust-42", 1L);

driver.close();

Драйвер работает в том же JVM, эмулирует время, watermarks, repartition — без реального брокера. Работает за миллисекунды.

Связь с главой XII

TopologyTestDriver уже упоминался в главе XII (лёгкие фреймворки). Здесь мы показываем его в контексте уровня 1 тестирования: unit-тест топологии, а не интеграционный тест.

• • •

IIIProperty-based testing: инварианты вместо примеров

Классический unit-тест проверяет конкретные примеры: «подал X — получил Y». Property-based test проверяет инварианты: «для любых X верно Y».

Hypothesis (Python)

Генерирует случайные события, проверяет инварианты. При нахождении контрпримера — минимизирует его до простейшего.

jqwik (Java)

Аналог для JVM. Генерация случайных событий, проверка свойств, shrink до минимального контрпримера.

Что проверять

Идемпотентность обработки, коммутативность агрегаций, монотонность счётчиков, отсутствие потерь при map/filter.

Fuzzing

Подача случайных байтов вместо валидных событий. Проверка, что десериализатор не падает, а обработчик отдаёт ошибку в DLQ.

// Пример property-based test на jqwik
@Property
void total_is_monotonic(@ForAll @Positive BigDecimal amount) {
    BigDecimal before = state.total();
    state.apply(new DepositEvent(amount));
    BigDecimal after = state.total();
    assertThat(after).isGreaterThanOrEqualTo(before);
}

Property-based testing ловит баги, которые никогда не придут в голову автору примеров: редкие комбинации, граничные случаи, негативные значения.

• • •

IVТестирование watermarks и event time

В главе VI мы обсуждали watermarks, late events, grace period. Как их тестировать? TopologyTestDriver позволяет продвигать event time вручную.

// Продвигаем время и проверяем закрытие окна
input.pipeInput("key-1", event1, Instant.ofEpochSecond(100));
input.pipeInput("key-1", event2, Instant.ofEpochSecond(200));

// Продвигаем watermark
driver.advanceWallClockTime(Duration.ofSeconds(300));

// Проверяем, что окно закрылось и результат появился
assertThat(output.readValueToMap()).containsEntry("key-1", 2L);

// Подаём late event ПОСЛЕ закрытия окна
input.pipeInput("key-1", lateEvent, Instant.ofEpochSecond(150));

// Проверяем, что late event обработан или отброшен согласно grace period
// ...

Что тестировать:

  1. Окна закрываются правильно. Tumbling, hopping, sliding, session — каждое по своим правилам.
  2. Late events попадают в grace period. Если event пришёл в пределах grace — он участвует в агрегации. Если позже — отбрасывается или уходит в side output.
  3. Idle sources не блокируют watermark. Если один источник замолчал, окна продолжают закрываться по остальным.
  4. Watermark propagation. Проверка, что минимум по входам работает корректно.
Связь с главой VI

Тестирование watermarks — это проверка того, что event time semantics работают правильно. Без этого теста вы не узнаете, что окна закрываются неверно, пока не увидите неправильные метрики в production.

• • •

VТестирование stateful processors

State store — ключевой элемент stateful-обработки. Его надо тестировать отдельно:

TestHarness (Flink)

Тестирование операторов в изоляции. Подача событий, проверка выхода, проверка state.

MockProcessorContext

Для Kafka Streams Processor API. Тестирование processor'а без топологии.

State store verification

После обработки событий — проверить, что state содержит ожидаемые значения. TopologyTestDriver даёт доступ к state stores.

TTL и cleanup

Проверка, что punctuator очищает устаревшие записи. Проверка, что tombstone пишется в changelog.

// Проверка state store после обработки
KeyValueStore<String, Long> store = driver.getKeyValueStore("customer-totals");
assertThat(store.get("cust-42")).isEqualTo(3L);

// Проверка TTL cleanup
input.pipeInput("key-1", event1);
driver.advanceWallClockTime(Duration.ofHours(25));
driver.runScheduledPunctuators();
assertThat(store.get("key-1")).isNull();

Что тестировать:

  1. State сохраняется. После обработки — в store лежат ожидаемые значения.
  2. State восстанавливается. Симуляция рестарта — state загружается из changelog.
  3. TTL cleanup работает. Через punctuator старые записи удаляются.
  4. Дедупликация работает. Повторное событие с тем же ID — не удваивает счётчик.
  5. Changelog пишется атомарно. Проверка, что события попадают в changelog в правильном порядке.
• • •

VIУровень 2: schema testing

В EDA схема — это закон. Если производитель молча изменит тип поля или удалит обязательный атрибут, потребитель упадёт. Тестирование эволюции схем должно быть встроено в CI/CD.

Режим Что проверяет Когда использовать
Backward Новый потребитель читает старые события Потребители обновляются первыми
Forward Старый потребитель читает новые события Производители обновляются первыми
Full Оба направления Независимый деплой
Backward Transitive Совместимость со всеми предыдущими версиями Event Sourcing, долгое хранение
# В CI-пайплайне перед деплоем
- name: Validate schema compatibility
  run: |
    curl -X POST $SCHEMA_REGISTRY_URL/compatibility/subjects/orders-value/versions/latest \
         -H "Content-Type: application/vnd.schemaregistry.v1+json" \
         -d @new-schema.json
    # Если incompatible — CI fails, деплой блокируется
Правило

Schema Registry — enforcement-механизм. Без CI-проверки правила совместимости — это просто надежда. С проверкой — гарантия, что breaking change не проскочит в production.

• • •

VIISnapshot testing схем

Дополнение к schema testing — snapshot testing:

  1. Baseline. Сохранить текущую схему как baseline в git.
  2. Сравнение. При изменении — сравнить новую схему с baseline.
  3. Diff. Показать, что изменилось: добавлены поля, удалены, изменены типы.
  4. Alert. Если breaking change — блокировать PR, требовать review.

Это простой и эффективный подход: вы явно видите, что меняется, и осознанно принимаете решение.

• • •

VIIIContract testing: API ↔ events

Когда API генерирует команды и события (глава XIII), контракт между API и event schema должен быть протестирован.

Pact

Consumer-driven contracts. Потребитель описывает ожидания, продюсер проверяет в CI. Прямая связь с главой III (Schema Registry).

Spring Cloud Contract

Для Java-экосистемы. Groovy DSL для описания контрактов, автоматическая генерация тестов.

Schema validation в CI

API Gateway валидирует команды против JSON Schema перед публикацией в Kafka.

Integration tests

Testcontainers для полного pipeline: API → Command → Event → Read model.

• • •

IXУровень 3: Testcontainers

Модульные тесты не проверят, как микросервис ведёт себя при сбое брокера, как происходит rebalance и как восстанавливается состояние из changelog. Для этого нужна локальная интеграционная среда.

Золотой стандарт — Testcontainers: реальный Kafka, Schema Registry, БД в Docker. Тест поднимает контейнеры, прогоняет события, проверяет итоговое состояние.

// Testcontainers + Kafka
@Container
KafkaContainer<?> kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.5.0"));

@Test
void should_rebalance_and_resume() {
    // 1. Запускаем consumer 1
    // 2. Обрабатываем часть событий
    // 3. Убиваем consumer 1
    // 4. Запускаем consumer 2 — он должен подхватить партиции
    // 5. Проверяем, что все события обработаны
}

Что тестировать:

  1. Rebalance. Убить consumer — проверить, что партиции перераспределены.
  2. Restart. Симуляция падения — state восстанавливается из changelog.
  3. Failure. Сбой брокера — consumer переподключается.
  4. Deduplication. Повторная обработка — счётчик не удваивается.
  5. Schema evolution. Новая схема — старые и новые события читаются.
• • •

XПроприетарные облачные брокеры

Что делать, если вы используете Kinesis, Pub/Sub или Event Hubs, у которых нет локальных open-source аналогов?

Платформа Локальный эмулятор Ограничения
AWS Kinesis LocalStack Не все фичи, latency отличается
Google Pub/Sub Официальный эмулятор Ограниченная поддержка ordering
Azure Event Hubs Нет полноценного эмулятора Тестирование против dev-сред
Практическое правило

Если у платформы есть локальный эмулятор — используйте его в CI. Если нет — используйте удалённые dev-среды с тщательной настройкой ACL и изоляцией по namespace. Но помните: это дороже и медленнее.

• • •

XIУровень 4: эфемерные кластеры

Когда нужно проверить производительность, нагрузку и восстановление после катастроф, локального Docker не хватит. Нужен полноценный кластер.

Клонирование прода

Копирование реальных топиков в тестовый кластер.

Плюсы: идеальное отражение реальности, учёт аномалий.

Минусы: риск утечки PII, нагрузка на прод, долгая репликация.

Синтетические данные

Генерация событий на основе схем из Schema Registry.

Плюсы: безопасно, быстро, можно создать редкие граничные случаи.

Минусы: не повторяет реальные паттерны и «грязь» прода.

Антипаттерн: «Единая общая тестовая среда»

Использование одного общего кластера для всех команд превращает его в «помойку». Тестовые топики с суффиксами -test-v2-final пересекаются с чужими тестами, данные устаревают, нагрузка от одной команды ломает тесты другой. Это «трагедия общин», которой нужно избегать.

Эфемерные кластеры как решение

Если организация инвестирует в инфраструктуру как код (IaC), лучший подход — программное создание временного кластера под каждый крупный тест:

  1. Тест запустился.
  2. Кластер поднялся (Terraform, Pulumi, Kubernetes Operator).
  3. Наполнился синтетикой или снэпшотом прода.
  4. Прогнал тесты.
  5. Кластер уничтожен.

Дорого, но это единственный способ гарантировать изоляцию и чистоту эксперимента.

• • •

XIIChaos testing: проверка на прочность

EDA — распределённая система. Она должна выживать при сбоях. Chaos testing проверяет это системно.

Chaos Monkey

Убивает случайные pod'ы в Kubernetes. Проверка, что сервис восстанавливается.

LitmusChaos

Chaos-эксперименты для Kubernetes. Готовые сценарии: pod-delete, network-loss, disk-fill.

Toxiproxy

Эмуляция сетевых сбоев: задержка, разрыв, потери пакетов. Проверка, что retry работает.

Gremlin

Managed chaos. Сценарии с гарантией безопасности. Для организаций без собственной chaos-платформы.

Что проверять:

  • Убить consumer. Партиции перераспределяются, обработка продолжается.
  • Убить брокер. Producer retry'ит, consumer переподключается.
  • Разорвать сеть. Circuit breaker срабатывает, система не падает.
  • Замедлить диск. RocksDB работает медленнее, но не падает.
• • •

XIIILoad testing: проверка под давлением

Инструмент Язык Особенность
k6 JavaScript Modern, встроенная метрика, cloud-версия
Gatling Scala DSL Мощный DSL, отчёты из коробки
JMeter Java + UI Классика, много плагинов, UI
Locust Python Простой DSL, распределённая нагрузка

Особенность EDA: нужно генерировать миллионы событий в секунду. Не все инструменты справляются. Иногда проще написать свой генератор на Kafka Producer API, который пишет в топик с заданной скоростью.

• • •

XIVReplay testing: уникальная суперсила EDA

Reprocessing — суперсила EDA (глава VI). Как тестировать?

  1. Захват. Сохранить реальные события из production в S3 или отдельный топик.
  2. Replay. Проиграть эти события в staging через новую версию сервиса.
  3. Сравнение. Сравнить выходы старой и новой версии. Если результаты совпадают — новая версия корректна.
  4. Обнаружение. Если результаты расходятся — найти причину. Это может быть bug или намеренное изменение.
Правило

Replay testing — единственный способ проверить, что новая версия обработки выдаёт те же результаты, что старая. Особенно важно для финансовых систем, где каждая цифра должна совпадать.

• • •

XVТестирование Saga и компенсаций

В главе VIII мы обсуждали Saga, компенсации, semantic locks. Как их тестировать?

Happy path

Все шаги саги выполняются успешно. Проверка, что финальное состояние корректно.

Компенсации

Сбой на шаге N — компенсации выполняются для шагов 1..N-1. Проверка, что состояние откатилось.

Idempotency

Повторный вызов шага — результат тот же. Проверка через дедупликацию.

Semantic lock cleanup

Зависшая сага — cleanup job снимает locks. Проверка через симуляцию таймаута.

// Temporal: тестирование компенсации
@Test
void should_compensate_when_payment_fails() {
    // 1. Запустить workflow с mock activities
    // 2. Симулировать сбой платёжного шага
    // 3. Проверить, что вызваны компенсации в обратном порядке
    // 4. Проверить, что финальное состояние консистентно
}
• • •

XVIProduction testing: безопасные эксперименты

Иногда единственный способ проверить, как микросервис поведёт себя под реальным давлением — запустить его в продакшене. Звучит страшно, но в EDA это делается безопасно:

  1. Изолированные Consumer Groups. Новый микросервис подключается к продакшн-топикам со своим уникальным consumer group. Он читает данные, но не влияет на работу старых потребителей.
  2. Теневые топики. Микросервис пишет результаты не в основные продакшн-топики, а в изолированные «теневые» топики, которые можно безопасно анализировать и удалять.
  3. Feature flags. Включение нового поведения только для небольшого процента трафика.
  4. Canary deployment. Новая версия обрабатывает 1% партиций, старая — 99%.

Это позволяет провести smoke test на реальных данных, проверить задержки и масштабирование, не рискуя сломать бизнес-процессы.

• • •

XVIICost и DX тестирования

Cost

Тестирование в EDA стоит денег:

Уровень Стоимость Когда оправдано
Unit + Property Практически $0 Всегда
Testcontainers CPU/RAM локально или в CI Каждый PR
Schema testing Практически $0 Каждый PR
Contract testing Практически $0 Каждый PR
Эфемерные кластеры $$–$$$ Перед релизом
Chaos testing $$ Раз в месяц
Load testing $$–$$$ Перед major релизом
Production testing Оплата инфраструктуры + риск Для критичных сервисов

Developer Experience

Плохой DX = тесты не пишутся. Что важно:

  • Быстрый локальный запуск. Docker Compose для полного стека за 30 секунд.
  • Параллелизация. Тесты должны бежать параллельно, а не последовательно.
  • Понятные ошибки. Что именно упало, где, почему.
  • IDE integration. Запуск тестов из IDE одной кнопкой.
  • Хорошие fixtures. Готовые тестовые данные и helpers.
Правило DX

Если тест пишется дольше, чем код, — тест не будет написан. Инвестируйте в инфраструктуру тестирования так же, как в production-инфраструктуру.

• • •

XVIIIЧеклисты и связь с циклом

Связь с предыдущими главами

Глава Фокус Связь с текущей главой
III. Идеальное событие Schema Registry, эволюция Schema testing, snapshot testing
V. Потоковая обработка TopologyTestDriver, state stores Уровень 1 тестирования
VI. Время в потоках Watermarks, event time Тестирование watermarks, late events
VII. Память микросервисов State store testing Тестирование stateful processors
VIII. Saga Компенсации, human-in-the-loop Тестирование Saga и компенсаций
X. BPC Простой poll-process-produce Базовый уровень тестирования
XI. Тяжёлые фреймворки MiniCluster, TestHarness Тестирование Flink jobs
XII. Лёгкие фреймворки TopologyTestDriver, EmbeddedKafka Тестирование Kafka Streams
XIII. API и UI Contract testing Testing между API и events
XIV. Налог на микросервисы Testing infrastructure Testing как часть платформы
XV. Как проверить «ненаблюдаемое» Testing EDA QA-перспектива всего цикла

Чеклист тестирования EDA

  • Unit-тесты для всех stateless-функций (filter, map, branching)
  • Property-based тесты для инвариантов (idempotency, монотонность)
  • TopologyTestDriver / TestHarness для тестирования топологии
  • Тесты watermarks и event time (окна закрываются правильно, late events обрабатываются)
  • Тесты state store (сохранение, восстановление, TTL cleanup)
  • Schema testing в CI (Backward / Forward / Full / Transitive)
  • Snapshot testing схем для явного контроля изменений
  • Contract testing между API и events (Pact, Spring Cloud Contract)
  • Testcontainers для локальных интеграционных тестов (rebalance, restart, failure)
  • Эмуляторы для проприетарных брокеров (LocalStack, Pub/Sub emulator)
  • Эфемерные кластеры для E2E-тестов
  • Chaos testing (Chaos Monkey, LitmusChaos, Toxiproxy)
  • Load testing (k6, Gatling, Locust)
  • Replay testing для проверки изменений обработки
  • Тесты Saga и компенсаций
  • Production testing через изолированные consumer groups и теневые топики
  • Cost и DX тестирования оценены
  • Быстрый локальный запуск (Docker Compose, IDE integration)

Ключевые выводы главы

  1. Тестирование EDA — это спектр. Unit → Integration → Contract → E2E. Каждый уровень проверяет своё.
  2. Пирамида с contract-слоем. Классическая пирамида дополняется contract testing как отдельным слоем.
  3. TopologyTestDriver / TestHarness — тестирование топологии в памяти без реального брокера.
  4. Property-based testing — инварианты вместо примеров. Ловит баги, которые никогда не придут в голову.
  5. Тестирование watermarks — проверка event time semantics, closing windows, late events.
  6. Тестирование state store — сохранение, восстановление, TTL cleanup, дедупликация.
  7. Schema testing в CI — enforcement-механизм. Без него правила — надежда.
  8. Snapshot testing схем — явный контроль изменений.
  9. Contract testing — Pact, Spring Cloud Contract для API ↔ events.
  10. Testcontainers — золотой стандарт локального интеграционного тестирования.
  11. Эфемерные кластеры — единственный способ гарантировать изоляцию и чистоту эксперимента.
  12. Chaos testing — проверка на прочность. Chaos Monkey, LitmusChaos, Toxiproxy.
  13. Load testing — k6, Gatling, Locust. Особенность EDA — генерация миллионов событий.
  14. Replay testing — уникальная суперсила EDA. Захват, replay, сравнение.
  15. Тестирование Saga — happy path, компенсации, idempotency, semantic lock cleanup.
  16. Production testing — изолированные consumer groups, теневые топики, feature flags, canary.
  17. Cost и DX — критичны для adoption. Плохой DX = тесты не пишутся.
Финальный тезис

Не существует единственного «правильного» способа тестировать событийно-управляемые микросервисы. Модульность микросервисов даёт вам свободу выбирать инструмент под задачу. Инвестируйте в автоматизацию создания тестовых сред и генерации данных, и ваш QA-процесс перестанет быть «бутылочным горлышком», превратившись в надёжный фундамент для непрерывной доставки.

Глава XV цикла «Событийно-управляемая архитектура». Источники: Apache Kafka, Confluent, Apache Flink, Testcontainers, Pact, Temporal, LitmusChaos, k6, jqwik, Hypothesis, практические кейсы production-систем, FinCodeCow experience.

FinCodeCow · Chapter XV

How to Test the Unobservable

A complete guide to testing event-driven microservices: from unit tests of topologies to production testing

In the world of synchronous REST APIs, testing boils down to a simple formula: send request — receive response — check status code. But in Event-Driven Architecture (EDA), this approach breaks against reality. There is no direct response. The event flies to the broker, is processed asynchronously, changes someone's state, and generates new events. How do you ensure the system works correctly if the result is stretched over time?

Fortunately, microservice architecture has high modularity. Where synchronous monoliths required deploying a "sheet" of dependencies for a single test, event-driven microservices allow isolating logic. In this chapter, we'll explore a multi-level EDA testing strategy: from pure functions to fully cloned clusters.

This is the fifteenth chapter of the series. It builds on Chapter III (schema testing, compatibility), Chapter V (TopologyTestDriver, state stores), Chapter VI (watermarks, event time), Chapter VII (state store testing, Changelog), Chapter VIII (testing compensations), Chapter X (BPC), Chapter XI (MiniCluster, TestHarness), Chapter XII (EmbeddedKafka), Chapter XIII (contract testing), and Chapter XIV (testing infrastructure). Here we close the cycle: from patterns and architecture to quality.

• • •

IThe EDA Testing Pyramid

Level Share Speed What It Verifies
Unit ~60% Milliseconds Stateless functions, topology, property-based
Integration ~30% Seconds–minutes Testcontainers, rebalance, failure, state store
Contract parallel Seconds Schema testing, Pact, snapshot testing
E2E ~10% Minutes–hours Ephemeral clusters, chaos, load, replay
The Pyramid Principle

The lower the level — the more tests and the faster they run. The higher — the fewer tests and the more expensive they are. For EDA this works the same, but with one difference: contract testing becomes a separate layer because contracts between producer and consumer are critical but don't fit into the classic pyramid.

• • •

IILevel 1: Functions and Topologies

Stateless Functions

Transformations, filtering, routing. They don't remember the past. Testing is trivial: fed an array of events — checked the output array. Boundary conditions, null, invalid schemas — main targets.

Stateful Functions

Aggregations and window computations. Here you need a mock of the state store (Key-Value store). Emulate the service's memory to verify how the function accumulates and collapses data over time.

// TopologyTestDriver for Kafka Streams
TopologyTestDriver driver = new TopologyTestDriver(topology, config);

TestInputTopic<String, OrderEvent> input = driver.createInputTopic(
    "orders", new StringSerializer(), new OrderEventSerializer());

TestOutputTopic<String, Long> output = driver.createOutputTopic(
    "customer-totals", new StringDeserializer(), new LongDeserializer());

input.pipeInput("order-1", new OrderEvent("cust-42", 150.0));

assertThat(output.readValueToMap()).containsEntry("cust-42", 1L);

driver.close();
• • •

IIIProperty-Based Testing

A classic unit test verifies concrete examples. A property-based test verifies invariants: "for any X, Y holds".

// Example of a property-based test with jqwik
@Property
void total_is_monotonic(@ForAll @Positive BigDecimal amount) {
    BigDecimal before = state.total();
    state.apply(new DepositEvent(amount));
    BigDecimal after = state.total();
    assertThat(after).isGreaterThanOrEqualTo(before);
}
• • •

IVTesting Watermarks and Event Time

// Advance time and verify window closure
input.pipeInput("key-1", event1, Instant.ofEpochSecond(100));
input.pipeInput("key-1", event2, Instant.ofEpochSecond(200));

driver.advanceWallClockTime(Duration.ofSeconds(300));

assertThat(output.readValueToMap()).containsEntry("key-1", 2L);
• • •

VTesting Stateful Processors

// Verify state store after processing
KeyValueStore<String, Long> store = driver.getKeyValueStore("customer-totals");
assertThat(store.get("cust-42")).isEqualTo(3L);

// Verify TTL cleanup
input.pipeInput("key-1", event1);
driver.advanceWallClockTime(Duration.ofHours(25));
driver.runScheduledPunctuators();
assertThat(store.get("key-1")).isNull();
• • •

VILevel 2: Schema Testing

# In CI pipeline before deploy
- name: Validate schema compatibility
  run: |
    curl -X POST $SCHEMA_REGISTRY_URL/compatibility/subjects/orders-value/versions/latest \
         -H "Content-Type: application/vnd.schemaregistry.v1+json" \
         -d @new-schema.json
• • •

VIISnapshot Testing of Schemas

  1. Baseline. Save current schema as baseline in git.
  2. Comparison. On change — compare new schema with baseline.
  3. Diff. Show what changed: fields added, removed, types modified.
  4. Alert. If breaking change — block PR, require review.
• • •

VIIIContract Testing: API ↔ Events

Pact

Consumer-driven contracts. Consumer describes expectations, producer verifies in CI.

Spring Cloud Contract

For the Java ecosystem. Groovy DSL for describing contracts, auto-generated tests.

• • •

IXLevel 3: Testcontainers

@Container
KafkaContainer<?> kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.5.0"));

@Test
void should_rebalance_and_resume() {
    // 1. Start consumer 1
    // 2. Process some events
    // 3. Kill consumer 1
    // 4. Start consumer 2 — it should pick up partitions
    // 5. Verify all events processed
}
• • •

XProprietary Cloud Brokers

Platform Local Emulator Limitations
AWS Kinesis LocalStack Not all features, different latency
Google Pub/Sub Official emulator Limited ordering support
Azure Event Hubs No full emulator Testing against dev environments
• • •

XILevel 4: Ephemeral Clusters

Antipattern: "Shared Test Environment"

Using a single shared cluster for all teams turns it into a "dumpster". Test topics with suffixes -test-v2-final overlap with other tests, data becomes stale, and load from one team's tests breaks another's. This is the "tragedy of the commons" to avoid.

Ephemeral clusters as the solution: test ran → cluster spun up → filled with synthetics → tests passed → cluster destroyed. Expensive but the only way to guarantee isolation.

• • •

XIIChaos Testing

Chaos Monkey

Kills random pods in Kubernetes. Verifies the service recovers.

LitmusChaos

Chaos experiments for Kubernetes. Ready scenarios: pod-delete, network-loss, disk-fill.

Toxiproxy

Emulates network failures: delay, break, packet loss. Verifies retry works.

Gremlin

Managed chaos. Safe scenarios. For orgs without their own chaos platform.

• • •

XIIILoad Testing

Tool Language Feature
k6 JavaScript Modern, built-in metrics, cloud version
Gatling Scala DSL Powerful DSL, out-of-the-box reports
JMeter Java + UI Classic, many plugins, UI
Locust Python Simple DSL, distributed load
• • •

XIVReplay Testing

  1. Capture. Save real production events to S3 or a separate topic.
  2. Replay. Replay these events in staging through the new version.
  3. Comparison. Compare outputs of old and new versions.
  4. Detection. If outputs diverge — find the cause.
• • •

XVTesting Saga and Compensations

// Temporal: testing compensation
@Test
void should_compensate_when_payment_fails() {
    // 1. Start workflow with mock activities
    // 2. Simulate payment step failure
    // 3. Verify compensations called in reverse order
    // 4. Verify final state is consistent
}
• • •

XVIProduction Testing

  1. Isolated Consumer Groups. New service reads production topics with its own consumer group.
  2. Shadow topics. Results written to isolated topics, safe to analyze and delete.
  3. Feature flags. Enable new behavior for a small percentage of traffic.
  4. Canary deployment. New version processes 1% of partitions, old — 99%.
• • •

XVIICost and DX of Testing

Cost

Level Cost When Justified
Unit + Property~$0Always
TestcontainersCPU/RAM locally or in CIEvery PR
Schema testing~$0Every PR
Contract testing~$0Every PR
Ephemeral clusters$$–$$$Before release
Chaos testing$$Once a month
Load testing$$–$$$Before major release
Production testingInfrastructure + riskCritical services

Developer Experience

  • Fast local startup. Docker Compose for the full stack in 30 seconds.
  • Parallelization. Tests should run in parallel, not sequentially.
  • Clear errors. What exactly failed, where, why.
  • IDE integration. Run tests from IDE with one click.
  • Good fixtures. Ready-made test data and helpers.
DX Rule

If writing a test takes longer than writing code — the test won't be written. Invest in testing infrastructure the same way you invest in production infrastructure.

• • •

XVIIIChecklists and Link to the Series

Link to Previous Chapters

Chapter Focus Link to This Chapter
III. The Anatomy of an Ideal EventSchema Registry, evolutionSchema testing, snapshot testing
V. The Mechanics of Stream ProcessingTopologyTestDriver, state storesLevel 1 testing
VI. Time in StreamsWatermarks, event timeTesting watermarks, late events
VII. The Memory of MicroservicesState store testingTesting stateful processors
VIII. The Mechanics of Business ProcessesCompensations, human-in-the-loopTesting Saga and compensations
X. Basic Producer/Consumer PatternSimple poll-process-produceBase level of testing
XI. Anatomy of Heavyweight FrameworksMiniCluster, TestHarnessTesting Flink jobs
XII. Anatomy of Lightweight FrameworksTopologyTestDriver, EmbeddedKafkaTesting Kafka Streams
XIII. A Bridge Between WorldsContract testingTesting between API and events
XIV. The Microservices TaxTesting infrastructureTesting as part of the platform
XV. How to Test the UnobservableTesting EDAQA perspective of the entire series

EDA Testing Checklist

  • Unit tests for all stateless functions (filter, map, branching)
  • Property-based tests for invariants (idempotency, monotonicity)
  • TopologyTestDriver / TestHarness for topology testing
  • Watermark and event time tests (windows close correctly, late events handled)
  • State store tests (save, restore, TTL cleanup)
  • Schema testing in CI (Backward / Forward / Full / Transitive)
  • Snapshot testing of schemas for explicit change control
  • Contract testing between API and events (Pact, Spring Cloud Contract)
  • Testcontainers for local integration tests (rebalance, restart, failure)
  • Emulators for proprietary brokers (LocalStack, Pub/Sub emulator)
  • Ephemeral clusters for E2E tests
  • Chaos testing (Chaos Monkey, LitmusChaos, Toxiproxy)
  • Load testing (k6, Gatling, Locust)
  • Replay testing for verifying processing changes
  • Saga and compensation tests
  • Production testing via isolated consumer groups and shadow topics
  • Cost and DX of testing evaluated
  • Fast local startup (Docker Compose, IDE integration)
Final Thesis

There is no single "right" way to test event-driven microservices. The modularity of microservices gives you the freedom to choose the tool for the task. Invest in automation of test environment creation and data generation, and your QA process will stop being a "bottleneck", becoming a reliable foundation for Continuous Delivery.

Chapter XV of the "Event-Driven Architecture" series. Sources: Apache Kafka, Confluent, Apache Flink, Testcontainers, Pact, Temporal, LitmusChaos, k6, jqwik, Hypothesis, production system case studies, FinCodeCow experience.