Software Development

Behavior Driven, Test Driven, Domain Driven Design

Ah, the joys of xDDs, you can never use too many, right? One of the many benefits of using event sourcing with CQRS is how well it facilitates using BDD style testing to drive our domain model design. Behavior driven development encourages writing tests which expose the use cases or scenarios required by the users and stakeholders. This is closely in line with the goals of domain driven design, which encourages us to model the domain abstractions in our code on a deep understanding of the business models and processes. BDD style tests are therefore quite well suited to the testing our domain model behaviors.

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.

Chris Nicola

Christian is a Principal Consultant at FuseSource specializing in developing enterprise software applications with an emphasis on software integration and messaging. His strengths include helping clients build software using industry best practices, Test Driven Design, ActiveMQ,Apache Camel, ServiceMix, Spring Framework, and most importantly, modeling complex domains so that they can be realized in software. He works primarily using Java and its many frameworks, but his favorite programming language is Python. He's in the midst of learning Scala and hopes to contribute to the Apache Apollo project.

Related Articles

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button