EventSourcing Testing Patterns
By
Mathias Verraes
Published on 28 May 2023
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.
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
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 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
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:
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.