Event Sourcing & CQRS · Event Sourcing in Practice
Testing Event-Sourced Systems
Given-when-then tests for aggregates and projections.
Event sourcing makes behavior tests expressive: Given past events, When a command is handled, Then expect new events (or an error). Projection tests: given events, expect read-model rows. These tests are fast (no database for aggregates), document business rules, and serve as regression tests for upcasters (old events → expected current shape).
Test shapes
| Test type | Given | When | Then |
|---|---|---|---|
| Aggregate behavior | Past events | Command | New events / error |
| Projection | Events | Projector runs | Read-model rows |
| Upcaster | Old event JSON | Upcast | Current schema |
| Process manager | Events | Policy reacts | Commands issued |
def test_cannot_withdraw_more_than_balance():
given = [{"type": "AccountOpened", "account_id": "a1"}, {"type": "MoneyDeposited", "amount": 100}]
with pytest.raises(DomainError, match="Insufficient funds"):
decide_withdraw(rehydrate(given), amount=150)
def test_withdrawal_emits_event():
given = [{"type": "AccountOpened", "account_id": "a1"}, {"type": "MoneyDeposited", "amount": 100}]
assert decide_withdraw(rehydrate(given), amount=40) == [{"type": "MoneyWithdrawn", "amount": 40}]
it("closes an account only when balance is zero", () => {
const given: Event[] = [{ type: "AccountOpened", accountId: "a1" }, { type: "MoneyDeposited", amount: 10 }];
expect(() => decideClose(rehydrate(given))).toThrow("Balance must be zero");
const settled = [...given, { type: "MoneyWithdrawn", amount: 10 } as Event];
expect(decideClose(rehydrate(settled))).toEqual([{ type: "AccountClosed" }]);
});
Common pitfalls
- Testing internal state instead of emitted events.
- No tests for old event versions.
- Projection tests that depend on a shared database state.
Key points
- Given-when-then on events
- Test upcasters with real old payloads
- Fast, database-free aggregate tests
Further reading: Martin Fowler: Given When Then
Practice questions
Why are given-when-then tests natural for event-sourced aggregates?
Answer
Aggregate state is defined by past events (given), behavior by commands (when) and outcomes by emitted events (then), so tests read like business rules.
How do you test upcasters?
Answer
Store real examples of old event payloads as fixtures and assert they upcast to the expected current schema.
Open in the app to mark lessons complete, take notes and track your progress.