Build event-sourcing + CQRS tests - the given-events / when-command / then-events aggregate test, replay determinism (same events produce the same state), event-versioning + upcasting, snapshot equivalence (replay-to-N vs snapshot-at-N must agree), retroactive event correction, and CQRS read-model projection tests (per-event projection deltas, idempotent + out-of-order apply, rebuild + zero-downtime swap, read-your-writes guard) with eventual-consistency convergence-window assertions in references/convergence-windows.md. Per martinfowler.com EventSourcing + CQRS references. Use when an event-sourced aggregate gains a new event type or a changed payload schema, when snapshots are introduced to shorten replay, when a read model is projected from the event stream, or when a documented convergence window needs a test.
72
90%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Tests for an event-sourced system verify replay determinism, snapshot equivalence, and version-evolution correctness - without them, the event log silently drifts from the rebuilt state.
time.now() / random-ID leaks at the source.The base test for any event-sourced aggregate is given-events / when-command / then-events: seed the aggregate with its prior events, handle one command, then assert the events it emits and the resulting state. Per Fowler - Event Sourcing, replay = "rebuild application state from scratch by replaying events in order."
def test_confirm_order_emits_order_confirmed():
# GIVEN - the aggregate's prior event history
history = [
OrderCreated(order_id="o1", customer="c1"),
ItemAdded(order_id="o1", sku="sku1", qty=2),
]
order = OrderAggregate.replay(history)
# WHEN - one command is handled
new_events = order.handle(ConfirmOrder(order_id="o1"))
# THEN - assert the emitted events, not only the final state
assert new_events == [OrderConfirmed(order_id="o1")]
# AND replay is deterministic: the same log rebuilds the same state
state_a = OrderAggregate.replay(history + new_events)
state_b = OrderAggregate.replay(history + new_events)
assert state_a == state_b
assert state_a.status == "confirmed"
assert state_a.line_items == [("sku1", 2)]If handle emits events derived from time.now() or random IDs,
state_a == state_b fails - the test catches non-deterministic replay
before it drifts the log from the rebuilt state.
Once this base test is green, layer on the operational checks - order independence within causality, snapshot equivalence, event versioning + upcasting, projection rebuild, retroactive correction, replay-mode side-effect suppression, and optimistic-concurrency appends - in references/snapshot-versioning-projections.md.
Per Fowler - CQRS, the read model is rebuilt from the write model's event stream; test it like the aggregate - deterministic, idempotent, rebuildable:
apply_all(events) twice yields the same
materialized state; current-time / random-ID reads in apply break it.ProductPriceChanged → {"sku1.price": 120}, etc.).Updated arrives before Created; if it assumes in-order (Kafka
per-partition), test that assumption end-to-end.new_proj.materialize() == old_proj.materialize() before switching
reads. Full swap mechanic + the read-your-writes guard (202 Accepted →
pending → active) are in
references/rebuild-swap-and-read-your-writes.md.Test each projection off one stream independently (search index, materialized SQL view, OLAP cube) so a flawed one doesn't mask the others.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Replay calls time.now() / random IDs | Non-deterministic | Make replay deterministic (Worked example) |
| Skip snapshot equivalence test | Snapshots silently diverge | Assert snapshot-at-N == replay-to-N (references) |
| No upcasting plan; rewrite event store on schema change | Audit loss; downtime | Versioned upcasters (references) |
| Real email/HTTP calls during replay | Duplicate side effects | Replay-mode flag (references) |
| Append without expected version | Lost updates from concurrent writers | Optimistic concurrency (references) |
| Skip the convergence-window test | "I changed it but the UI shows the old value" | Assert the window (references/convergence-windows.md) |
| Treat the projection as always-current | Subtle stale reads in prod | Document + assert the window |
| No rebuild test for a projection | Schema migration becomes risky | Rebuild + swap tests (projection section) |
saga-transaction-tests) for those.saga-transaction-tests -
cross-aggregate transactions.