OpenCQRS 2.0: Tests That Read Like the Domain - EventSourcingDB
Skip to content
Initializing search
Getting Started
Fundamentals
Deployment and Operations
Reference
Client SDKs
Extensions
MCP Server
Best Practices
Implementation and Development
Data Management and Performance
Operations, Compliance and Infrastructure
Common Issues
Blog
Categories
Privacy Policy
Legal Notice
OpenCQRS 2.0: Tests That Read Like the Domain¶
In most software, testing is something you bolt on after the fact: you write the code, then you write tests to convince yourself the code does what you hoped. Event Sourcing quietly inverts that relationship. Because behavior is expressed as events, a test can read like a sentence about the domain: given these past events, when this command arrives, then these new events should follow. The test stops being scaffolding around the code and becomes the specification of what the system is supposed to do.
On June 26th, our friends at Digital Frontiers shipped OpenCQRS 2.0 , and the change we keep coming back to leans all the way into that idea. The release modernizes plenty under the hood, but the part that matters most day to day is the redesigned testing support – a fluent DSL that makes those given/when/then specifications read less like test code and more like a conversation with the domain. Let's start with why that conversation is worth having at all.
Why Event Sourcing Tests Are Different¶
Think about how you test a typical state-based service. You set up a database, or you mock the repository that talks to it. You arrange rows, call a method, then read the rows back and assert on them. Half the test is infrastructure, and the mocks encode your assumptions about collaborators rather than the behavior you actually care about. When such a test breaks, it is often the scaffolding that broke, not the logic.
Event Sourcing removes most of that ceremony. A command handler in this world is close to a pure function: you feed it the events that already happened and the command that just arrived, and it decides which new events should be appended. There is no hidden state, no clock you cannot control, and no network in the middle. The same inputs always produce the same decision.
That property is a gift for testing. A test can mirror the handler exactly: arrange the past as a list of events, act by sending a command, and assert on the events that come out. There is nothing to mock, because there are no collaborators standing between you and the decision. We have made this case before in Testing Without Mocks , and it holds especially well here, where the events are both the input and the output.
There is a second benefit that is easy to miss. A test written this way is documentation that cannot rot, because it runs. A new teammate reading "given a confirmed order, when a shipment is requested, then an OrderShipped event follows" learns the rule and gets proof that the rule still holds, in the same breath. The specification and the evidence that the code obeys it are the same artifact, which is something a state-based test full of mocks can rarely claim.
It also explains why a CQRS framework lives or dies by its testing story. If the whole point is that behavior is expressed as events, then the way you write tests is the way you talk about behavior. Frank Scheffler made exactly this argument when we sat down with him; if you want the background on how OpenCQRS approaches CQRS without forcing the aggregate pattern, our interview on OpenCQRS is a good place to start.
The Old Way Worked, but the Phases Were Implicit¶
OpenCQRS 1.0 already understood this. Its test fixture let you write a given/when/then test as a single chain of method calls, and for the simplest cases it read just fine:
fixture.givenNothing()<br>.when(command)<br>.expectSuccessfulExecution()<br>.expectSingleEvent(event);
This did the job. The friction was subtler than any missing feature: every assertion began with the same expect prefix, and that repetition was the visible symptom of a structural issue underneath. Setup, execution, and assertion all hung off one flat surface, so the given/when/then phases the test was meant to express were never actually separated in the code. They were a convention you kept in mind, not a structure the API enforced.
That is the problem the redesign went after. A test that needs to say "given a book that is already lent, when someone tries to borrow it, then the command fails with a specific exception" should be assembled from three distinct steps that mirror that sentence – arrange, act, assert – rather than a single chain in which the boundaries between them are left for the reader to infer.
A DSL That Reads Like the Domain¶
OpenCQRS 2.0 replaces the old fixture API with a fluent Given/When/Then DSL , and the difference is immediately visible. The same simple test now reads as three clearly separated...