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
91%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
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.