EventSourcing Testing Patterns

By Mathias Verraes
Published on 28 May 2023



Below are the common patterns I use when writing tests for an EventSourced application. I’m documenting them here because I mention them regularly and I needed a place to point people to. I believe I talk about them in some of my Temporal Modelling talks.

Surely these patterns deserve more explanation. Tweet me with your questions and I will amend this post.

Command handlers

Given Events (0 or more)
When Command 
Then Events (1 or more)

(example)
Given ItemWasStocked
When PurchaseItem 
Then ItemWasPurchased
Then StockWasReduced

Sometimes no Event is produced, for example for idempotency, because it’s a legacy Command that no longer requires a result, or any other reason. In those cases, I prefer to make this explicit in my tests:

Given Events (0 or more)
When Command
ThenNothing

Commands can fail:

Given Event(s)
When Command
ThenThrow Exception

(example)
Given ItemWasStocked 
Given ItemWasPurchased
When PurchaseItem 
ThenThrow ItemIsOutOfStock

Projections

Given Events (0 or more)
When Query 
Then Response

(example)
Given LocationWasTracked
Given LocationWasTracked
Given LocationWasTracked
WhenAsked FetchWalkingDistance
ThenRespondWith WalkingDistance

I like to make Queries explicit, which I’ve writte, about in my post on Domain Queries.

Process managers

Process Manager are reactors, they listen to Event Streams and produce various outcomes.

Process managers can produce new Events. For example, a Summary Event, or an interpretation of other Events.

Given Events (1 or more)
Then Event

(example)
Given AddressWasChanged
Given AddressWasChanged
Given AddressWasChanged
Given AddressWasChanged
Given AddressWasChanged
Then PossibleFraudWasDetected

A common pattern is that a Process Managers reacts to a Passage Of Time Event:

(example)
Given Midnight(date=2018-05-28)
Then GDPRRegulationWasActivated

Process managers can produce new Commands:

Given Events (1 or more)
Then Command

(example)
Given AddressWasChanged
Given AddressWasChanged
Given AddressWasChanged
Given AddressWasChanged
Given AddressWasChanged
Then BlockAddressChangesForUser

This allows to test in one system if it produces the right Commands, and in the other system how it handles Commands.

Process Managers are the perfect place to deal with side effects such as sending emails, doing filesystem operations, dealing with third-party APIs, and any I/O you need. You may want to use mocks, and test whether the actual I/O happens elsewhere.

Given Events (1 or more)
Then I/O

(example)
Given PossibleFraudWasDetected
Then SendEmailToInfosecTeam

Notes

  • The tests are all at the abstraction level of the domain, and use the Ubiquitous Language.
  • The messages use the Natural Language Message Names pattern.
  • Command Handlers often defer the work to Aggregates, but I rarely test my Aggregates directly. That allows me to change the underlying implementation, without having to change the tests.
  • In fact, all these tests should change if and only if the domain changes (or if bugs are discovered).
  • I consider all of these unit tests: they test a single unit of behaviour, a single example in the language of the domain.

Testing Projection State is an anti-pattern

When testing Projections, the much more commonly used patterns involve testing the state of the Projection, or the changes to the that state:

Given Events
When Event
Then State

Given State
When Event
Then State

(example)
Given Invoice {status = unpaid}
When InvoiceWasPaid
Then Invoice {status = paid}

I consider this an anti-pattern when doing domain-level testing. The state of the system is an implementation detail. I conceptualise a system as a black box that receives and produces messages (mostly of the types Commands, Queries, Events). If I’m at this abstraction level, I should only test for the observable behaviour of the system. If you think about it, the client of the system (such as a UI or third-party system) is only interested in whether it gets the right Responses to its Queries, and not in what you do underneath.

It has practical benefits:

  • You want your APIs not to change much. Query tests preserve the contract of the API, but allow you to change the underlying state easily. When I test the state, I couple my domain tests to my implementation choice, which makes the tests more brittle.
  • I can implement these Query tests using an in-memory eventstore and in-memory state. That means I can do lots of very fast and cheap tests. I can write these tests before writing any code or dealing with databases, so I can use them to explore my domain model and validate it before committing to it. I can also easily explore various edge cases that come up later.

Patterns are only anti-patterns in a context: It does make sense to test for state changes when doing integration testing. After I’m happy with the model and the logic, I want to test if my database schema and queries are correct. I also have integration tests for non-functional requirements such as retry behaviour. These tests are slower and more cumbersome. I only want a few of these, to test the integration itself, but the real bulk of tests for the domain behaviour is going to be done with Query tests.