In my previous posts, I showed the syntax for context/specifications using Machine.Specifications (or MSpec for short) and how to use an auto mocking container in conjunction with this excellent Behavior-Driven Development (BDD) framework. For this post, I want to show you one of the nice features of MSpec called behaviors.
Suppose we have to create some sort of specification that validates the format of an e-mail address. We typically use some regular expression in order to ensure that a specified e-mail address is properly formatted.
I guess this is pretty common and straightforward. One way to provide some unit tests for this particular piece of code is to check a whole number of e-mail addresses that either pass or fail the specification. The following example shows only a couple of scenarios:
A slightly more concise approach for these kind of unit tests can be accomplished by utilizing a feature of any decent unit test framework called row tests. With this approach we can, at the very least, reduce the number of asserts we have to write for each unit test.
Notice that I explicitly called both of these approaches unit tests as they don’t have much to do with BDD in my opinion. I’m not saying that using regular unit tests is a bad thing, but with behavior-driven development context is king. So these unit tests are perfect examples of ‘context betrayal’ when following the BDD approach.
Lets see what MSpec can bring to the table for these kind of scenarios:
In order to escape ‘context betrayal’, we’ve split up every context into a separate context/specification. In order to reduce the amount of effort caused by duplicate code, we stripped the context setup to the bare minimum (just a particular e-mail address in this case). The observations are isolated into MSpec behaviors which provides a very readable description of their outcome. Lets take a look at what is needed in order to get these behaviors to work.
But first lets take at look at the abstract base class that we’ve used for the context/specifications we’ve just shown.
We abstracted as much as possible into this base class in order to remove duplication in the context/specifications. The creation of the subject-under-test and the calling of its IsSatisfiedBy method, but the important one is the declaration of the Result field. This field contains the outcome of the IsSatisfiedBy method. Finally, lets have a look at the behaviors themselves:
public class EmailSpecification
{
private const String EmailRegexPattern = @".. SOME_REGEX_PATTERN ...";
public Boolean IsSatisfiedBy(String candidate)
{
var regex = new Regex(EmailRegexPattern);
return regex.IsMatch(candidate);
}
}
In order to create an MSpec behavior, we just have to create a separate class that we decorate with the Behaviors attribute. Also notice that we have the same declaration of the Result field. MSpec ensures that this field gets initialized with the value of the other Result field that is set in the base class of the context/specifications. Note that you don’t necessarily need to put this field in a base class. You can have that field in every context/specification if you’d like (not sure why) as long as the names match with the fields used in the defined behaviors.
I personally like the way how the MSpec contributors tried to solve testing the same logic with different input patterns and the syntax they provided to back this up.