Testing an Event Sourced Aggregate Root
I’ve mentioned tests twice now and not acted on them yet… Since we now have enough infrastructure in place to attempt to exercise a “Slice” down through our Write Model, let’s give it a try.
Let’s look back at our EventStormed “Start Creating Roast Schedule” Command:
The Team could translate this into this Behavior-Driven Development (BDD) Scenario:
Scenario: Setting up a brand new roast schedule
Given there is no roast schedule created for next week
When I start creating a roast schedule
Then an empty roast schedule is created
We’ll get into the invariant “no roast schedule created for next week” later, since it’s important for the Team to work with Nick to really figure out what it means to check if there is already a Roast Schedule when Nick starts scheduling his coffee roasting for the following week.
For right now, let’s focus on how we’re going to exercise the Event Sourced Aggregate Root, trying to stay close to this BDD-style of testing. The techniques I’m going to show you are not original, they come from Greg Young’s DDD/CQRS/Event Sourcing training here and from Mark Nijhof’s Fohjin project here. We’re going to use my preferred xUnit, Moq, and FluentAssertions testing framework “stack” to write tests.
With the Event Sourced Aggregate Root, we want to test that a Command successfully transitions the state of the Aggregate, and emits an Event signifying the success of this state transition. By designing the RoastSchedule
Aggregate the way that we have, an Event will not be created if the Command was not successful. Remember, we can say ‘No’ to Commands. Events are business facts that definitely happened.
We’re going to use a base class that allows us to ensure that Given an Aggregate, When a Command is issued to our Aggregate, Then an Event (or set of Events) is published.
Using a BDD naming convention, we’ll call this base class a Specification<TAggregateRoot, TCommand>
:
public abstract class Specification<TAggregateRoot, TCommand>
where TAggregateRoot : AggregateRoot, new()
where TCommand : Command {
...
}
We’ll organize our tests using folders that represent Scenarios:
Our Given will be used to establish the state of the System Under Test… in this case the RoastSchedule
Aggregate Root. Given, in an Event Sourced test, is represented by a collection of Events. We can put our System Under Test in any state simply by instantiating a List<Event>
that, by replaying, would get the Aggregate into the desired Given state:
public abstract class Specification<TAggregateRoot, TCommand>
where TAggregateRoot : AggregateRoot, new()
where TCommand : Command {
protected virtual IEnumerable<Event> Given() {
return new List<Event>();
}
...
}
Notice that when we implement a Scenario<TAggregateRoot, TCommand>
, we will either override Given()
to yield
the Events to build up the starting state of the Aggregate or we will just return a new List<Event>()
to signify that nothing has happened yet to the Aggregate. The latter implementation will be used for testing the StartCreatingRoastSchedule
Command since it will be the first Event in the RoastSchedule
Aggregate’s lifetime.
Next would be our When. When will be *When TCommand
is sent to the ICommandHandler<TCommand>
that we are testing. So the When()
method will return a Command
:
public abstract class Specification<TAggregateRoot, TCommand>
where TAggregateRoot : AggregateRoot, new()
where TCommand : Command {
protected virtual IEnumerable<Event> Given() {
return new List<Event>();
}
protected abstract TCommand When();
...
}
So to test When I start creating a roast schedule
, we’ll have When()
return a StartCreatingRoastSchedule
Command:
public class When_starting_to_create_a_new_roast_schedule : Specification<RoastSchedule, StartCreatingRoastScheduleCommand> {
private readonly Guid Id;
public When_starting_to_create_a_new_roast_schedule() {
Id = Guid.NewGuid();
}
protected override StartCreatingRoastScheduleCommand When() {
return new StartCreatingRoastScheduleCommand(Id);
}
...
}
We also need a concrete StartCreatingRoastScheduleCommandHandler
to handle When:
public abstract class Specification<TAggregateRoot, TCommand>
where TAggregateRoot : AggregateRoot, new()
where TCommand : Command {
protected virtual IEnumerable<Event> Given() {
return new List<Event>();
}
protected abstract TCommand When();
protected abstract ICommandHandler<TCommand> CommandHandler();
...
}
Here’s where we override CommandHandler()
to return StartCreatingRoastScheduleCommandHandler
:
public class When_starting_to_create_a_new_roast_schedule : Specification<RoastSchedule, StartCreatingRoastScheduleCommand> {
private readonly Guid Id;
public When_starting_to_create_a_new_roast_schedule() {
Id = Guid.NewGuid();
}
protected override StartCreatingRoastScheduleCommand When() {
return new StartCreatingRoastScheduleCommand(Id);
}
protected override ICommandHandler<StartCreatingRoastScheduleCommand> CommandHandler() {
return new StartCreatingRoastScheduleCommandHandler(MockRepository.Object);
}
...
}
Notice MockRepository.Object
. Our StartCreatingRoastScheduleCommandHandler(IRepository repository)
has a dependency that our test needs to accommodate. We’ll use Moq to mock it. Most importantly, we’re going to set up a .Callback
on the setup for the mock of IRepository<TAggregateRoot>
that makes sure the original TAggregateRoot
property on Specification
is replaced by the TAggregateRoot
that published the Events:
public abstract class Specification<TAggregateRoot, TCommand>
where TAggregateRoot : AggregateRoot, new()
where TCommand : Command {
protected virtual IEnumerable<Event> Given() {
return new List<Event>();
}
protected abstract TCommand When();
protected abstract ICommandHandler<TCommand> CommandHandler();
protected TAggregateRoot AggregateRoot;
// setup Moq in Specification's constructor...
protected Mock<IRepository<TAggregateRoot>> MockRepository;
protected Specification() {
...
MockRepository = new Mock<IRepository<TAggregateRoot>>();
MockRepository.Setup(x => x.GetById(It.IsAny<Guid>())).Returns(AggregateRoot);
// when AggregateRoot is saved, replace Specification.AggregateRoot with
// AggregateRoot that has been "acted" on
MockRepository.Setup(x => x.Save(It.IsAny<TAggregateRoot>(), It.IsAny<int>()))
.Callback<TAggregateRoot, int>((x, _) => AggregateRoot = x);
...
}
}
Next up - let’s make our Specification
runnable…
I will be releasing most of this content only to subscribers. Make sure you sign up by clicking the big red button!
Related Posts:
- Aggregate Event Persistence
- Event Store
- Command Handlers
- Implementing an Event Sourced Aggregate
- Design Level Continued
- Complexity and Cost
- Design Level EventStorming
- People and Commands
- Hotspots
- Domain Discoveries
- Big Picture EventStorming
- The Domain - First Pop Coffee Company
- More Efficient Domain Modeling with EventStorming
- Where do you find resources for learning DDD, CQRS, Event Sourcing, and EventStorming?
- Has the code devolved into a big ball of mud?… What can you do about it?
- A Better Way to Project Domain Entities into DTOs
- Exposing IQueryable in a CQRS Query Stack
- A Pattern to Decouple your Aggregates from their Clients
- Erlang-style Supervisors in C# with Akka.NET and the Actor Model