An Executable Specification
In the last post, we started to write tests in a way that would result in a readable specification. Borrowing from Greg Young and Mark Nijhof, we created a base class called Specification
that allows us to test our Event Sourced Aggregate Roots in a Given-When-Then format.
We left off with our Specification<TAggregateRoot, TCommand>
class’s Given()
and When()
methods defined:
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);
...
}
}
And an actual test case partially written that sets up a fixture using Specification
:
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);
}
...
}
But this doesn’t execute. We need to now assert that Then an empty roast schedule is created
. We can tie this all the way back to our orange stickie in the EventStorming done in the beginning of this whole project: “Roast Schedule Created.” This means we need to assert that a RoastScheduleCreatedEvent
was published by RoastSchedue
, signifying that StartCreatingRoastScheduleCommand
was successful.
Since any class that implements AggregateRoot
tracks un-committed (not persisted to the Event Store yet) Events in its List<Event> _changes
property, we simply need to test that a RoastScheduleCreatedEvent
is the last Event in the RoastSchedule._changes
collection.
We add a PublishedEvents
property to Specification<TAggregateRoot, TCommand>
base class to collect all of the Events that are published during the execution of a Specification:
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;
protected Mock<IRepository<TAggregateRoot>> MockRepository;
// collects published events for assertion
protected IEnumerable<Event> PublishedEvents;
protected Exception CaughtException;
protected Specification() {
...
}
}
Also, let’s make sure we can actually say Then
an Event happened. We’ll simply inherit our testing framework’s test attribute (xUnit’s [Fact]
in this case):
public class ThenAttribute : FactAttribute { }
Finally, we exercise our Given-When-Then by instantiating TAggregateRoot
, loading it from its Event history (if we are setting it up in a certain state), and sendingTCommand
to the ICommandHandler<TCommand>
:
public abstract class Specification<TAggregateRoot, TCommand>
where TAggregateRoot : AggregateRoot, new()
where TCommand : Command {
...
protected Specification() {
...
AggregateRoot = new TAggregateRoot();
AggregateRoot.LoadFromHistory(Given());
try {
CommandHandler().Handle(When());
PublishedEvents = new List<Event>(AggregateRoot.GetUncommittedChanges());
}
catch (Exception exception) {
CaughtException = exception;
}
}
}
Our Specification:
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
Gets fully written like this:
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);
}
[Then]
public void Then_a_roast_schedule_created_event_will_be_published() {
PublishedEvents.Last().As<RoastScheduleCreatedEvent>().Id.Should().Be(Id);
}
}
And it passes:
The results of the passing test don’t read exactly like our BDD specification, but more work could be done to make them do that. This would make them truly “executable specifications” that could be handed to First Pop Coffee to confirm that its business requirements are being met.
I will be releasing most of this content only to subscribers. Make sure you sign up by clicking the big red button!
Related Posts:
- Testing an Event Sourced Aggregate Root
- 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