# Testing Event-Sourced Systems

This guide explains how to test event-sourced systems. It covers **unit tests** for application logic, **integration tests** involving the event store, and practical strategies for **managing test data** and verifying **event-driven behavior**. When the event store is focused solely on **storing and retrieving events**, testing primarily concerns the **application layer** – and how it interacts with the event store.

Event-sourced systems require a shift in mindset when it comes to testing. Instead of verifying the result of a **state mutation**, the focus is on verifying that the **correct events were emitted** – or that a given stream of events leads to a **specific outcome**. This allows for a clear separation between **decision logic** and **persistence**.

## Unit Testing with Given-When-Then

At the core of an event-sourced system is **application logic** that processes events and emits new ones. This logic can be tested without involving any infrastructure. The most common pattern is **Given-When-Then**:

- **Given** a history of past events
- **When** a command or action is applied
- **Then** expect a specific set of resulting events

This approach makes tests **readable**, **focused**, and **deterministic**. It also reflects how event-sourced systems operate in production: decisions are based on **history**, not on **mutable state**.

For example, given a stream of events that shows a book was acquired and borrowed, attempting to borrow it again might raise an error – or emit a **rejection event**. The test would specify the initial events, apply the action, and assert that the result matches the expected **domain behavior**.

Because the event store does not evaluate **business logic**, these tests are entirely independent of it. They focus on the correctness of **aggregates**, **command handlers**, or **domain services**, and are typically **fast and isolated**.

## Integration Testing with the Event Store

While unit tests validate **decision logic**, integration tests verify the interaction between the **application** and the **event store**. This includes:

- **Writing events** to the event store
- **Reading** them back to verify persistence
- **Observing events** through the streaming API
- Asserting on **ordering**, **structure**, and **schema compliance**

An event store with short startup and shutdown times is well suited for **automated tests**. It can be started and stopped before and after each test, ensuring **isolation** and **reproducibility**.

There are two typical options:

- **Using Docker**: Launch the event store in a container for each test or test suite, then remove it afterwards.
- **Using a binary**: Start the event store as a subprocess with a temporary data directory, which creates an isolated store for each test.

Both approaches allow for a **clean database state** in each test, without leftover data or side effects. An event store that requires no **external dependencies** or **brokers** integrates well into **test environments** of any scale.

Tests can interact with the event store via its **API** or a **client SDK**. They can verify that written events are **persisted correctly**, that **schema validation** is enforced, and that **event observation** works as expected – including **reconnection** and **continuation behavior**.

> **Try it with EventSourcingDB**
>
> EventSourcingDB starts and stops quickly and requires no external dependencies. Run it in Docker, or start the pre-built binary with the **`--data-directory-temporary`** flag, and talk to it via HTTP or one of the **[official SDKs](/docs/eventsourcingdb/sdks)** – see **[Running EventSourcingDB](/docs/eventsourcingdb/running-eventsourcingdb)**.

## Verifying Replays and Projections

One of the strengths of event sourcing is the ability to **replay events**. Tests can take advantage of this by verifying that a **projection** or **read model** can be rebuilt from scratch.

This typically involves the following steps:

1. **Write a series of domain events**
2. **Run the projection logic** against them
3. **Assert that the resulting state matches expectations**

These tests are useful for ensuring that **projections** remain consistent with **event history**, and that they continue to work correctly as new **event types** or **versions** are introduced.

Replay-based tests can also be used to simulate **system upgrades**, perform **migrations**, or validate **backward compatibility**.

## Ensuring Idempotency and Safe Reprocessing

Event handlers that consume events from the event store should be **idempotent** – meaning they can safely process the same event more than once. Integration tests can verify this behavior by applying the same event twice and asserting that the **side effect is not duplicated**.

Depending on the system's architecture, tests may also verify:

- **Deduplication** based on event ID
- **Persistence** of last-processed event ID
- **Correct resumption** after a simulated crash or disconnect

These tests help ensure that **event handling** remains **reliable and consistent** under real-world conditions.

## Managing Test Data

Because events are **immutable** and event stores accumulate **history**, **test data management** is especially important. Each test should operate on a **clean database instance** or use **unique subject paths** to avoid collisions.

Strategies include:

- **Generating random subject identifiers** per test
- **Using isolated event types** for test data
- **Cleaning up** after tests by deleting the temporary data directory
- **Relying on process isolation** to ensure clean state

Tests need no complex **schema setup**, **migrations**, or **seed data**, which simplifies **test preparation**: test cases can define all necessary context through **events alone**.

## Testing in Event-Sourced Systems Is Self-Describing

Unlike traditional systems where state must be inspected or reverse-engineered, event-sourced systems offer **transparency by design**. Every action is recorded, and every outcome can be traced to its cause. This makes tests **easier to write**, **reason about**, and **maintain**.

Instead of verifying **implementation details**, tests focus on **behavior and outcomes**. The **event log** becomes both the **test input** and the **assertion target**. Combined with a reliable event store, this leads to systems that are **testable by default** – not just through extra effort.
