Behavior Driven, Test Driven, Domain Driven Design
After seeing Greg Young’s demonstrate how to write BDD style tests for CQRS in this video—-a word of warning though, it’s over 6 hours and I’m not quite sure where the testing part started—-I started working on applying it to my own domain model which uses nCQRS. I also switched from nUnit to MSpec as my preferred tool for testing, partly because it has better syntax and support for BDD. Upon doing so I needed to create a simple base test context class to facilitate the testing. After a couple of refactorings I’m very satisfied with the result and I wanted to share it.
I also think seeing these how these tests work goes a long way towards understanding how CQRS/event sourcing systems work (testing is believing). The generic pattern typically followed when applying BDD to an event sourced CQRS model is as follows:
Given a history of events (the current state of the domain model) When this command is executed (the behavior we want to test) Then the following events should have occurred (the expectations we have for this test)
I think it’s worth noting that I’m not necessarily saying that Give/When/Then is the only right way to approach BDD, and I’m aware there is some contention around conforming to this style. However, in this scenario they fit and so it certainly makes sense to use them to describe the tests. However, since I’m going to use MSpec for my tests, which doesn’t support this style of writing or reporting the test cases, I won’t be conforming to it either and I don’t really think it’s of any importance either. All that is really important is how these tests drive our design and expose our use cases. MSpec provides the following conventions for setting up our tests:
Establish
is used to provide the context for a test, this is where I set up the historic events and the domain state. Because
is used to execute the action under test, in this case we are executing a command for the domain which will be handled by our command handler. It
is the way we define our expectations of what should have happened. I’ve created a custom Should
extension method to test the event results.
Here is an example of a test I wrote in this style:
public class when_updating_a_feature_for_a_product : domain_context<SetFeaturesForProduct, SetFeaturesForProductHandler>
{
static Product _product;
Establish context = () =>
{
_product = Given<Product>(History.product_created, History.product_feature_added);
ExpectedEvents = 1;
};
Because of = () => When(new SetFeaturesForProduct
{
ProductId = _product.EventSourceId,
Features = new Dictionary<string, object> { {'feature1', 'updated feature value'} }
});
It should_have_updated_the_feature_value = () => Events.ShouldHave<ProductFeatureUpdated>(
x => x.Feature == 'feature1',
x => x.Value == 'updated feature value');
It should_do_nothing_else = () => Events.Count().ShouldEqual(ExpectedEvents);
}
Pretty clear and concise if I do say so myself (and I do). The base class for these tests provides it’s own base Establish
context which is run first and sets up the scaffolding for nCQRS to execute for the test. It’s generic parameters define the ICommand
type and ICommandExecutor<TCommand>
that the test will be based on.
Finally we hook into the events using nCQRS’ built in delegate handler. Here’s the Gist:
public static class DomainSpecificationExtensions
{
public static void ShouldHave<T>(this IEnumerable<ISourcedEvent> source, params Predicate<T>[] conditions) where T : class, ISourcedEvent
{
T actualEvent = (T) source.FirstOrDefault(x => x is T);
if (actualEvent == null)
throw new SpecificationException(string.Format('{0} did not happen as expected', typeof (T).Name));
actualEvent.ShouldMatch(conditions);
}
public static void ShouldMatch<T>(this T actual, params Predicate<T>[] conditions) {
foreach (var condition in conditions)
{
if (!condition.Invoke(actual))
throw new SpecificationException(string.Format('{0} did not match the condition {1}', typeof(T).Name,
condition.Method.GetMethodBody()));
}
}
}
This base class even supports command excution involving multiple aggregate roots. When we call Given()
an aggregate root is created, initialized from the event history and any new events are then tracked via the delegate call. It’s also added to the mock UoW class used by the command handler so the handler will be able to load it. It is extremely easy to write consistent, repeatable BDD style tests using this pattern, and because we verify each event and ensure no other events occurred we are testing not only what has happened but also what hasn’t happened, something that is typically very hard to verify.
Reference: Behavior Driven, Test Driven, Domain Driven Design from our NCG partner Chris Nicola at the lucisferre blog.