Behaviors with MSpec

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.

public class EmailSpecification
{
    private const String EmailRegexPattern = @".. SOME_REGEX_PATTERN ...";

    public Boolean IsSatisfiedBy(String candidate)
    {
        var regex = new Regex(EmailRegexPattern);
        return regex.IsMatch(candidate);
    }
}

 

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:

[TestFixture]
public class When_validating_an_email
    : ContextSpecification<EmailSpecification>
{
    protected override EmailSpecification Create_subject_under_test()
    {
        return new EmailSpecification();
    }

    [Test]
    public void ShouldBeSatisfied()
    {
        Assert.That(SUT.IsSatisfiedBy("[email protected]"));
        Assert.That(SUT.IsSatisfiedBy("[email protected]"));
    }

    [Test]
    public void ShouldNotBeSatisfied()
    {
        Assert.That(SUT.IsSatisfiedBy("one_two.com"), Is.False);
        Assert.That(SUT.IsSatisfiedBy("one_two@"), Is.False);
    }
}

 

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. 

[TestFixture]
public class When_validating_an_email__approach_2
    : ContextSpecification<EmailSpecification>
{
    protected override EmailSpecification Create_subject_under_test()
    {
        return new EmailSpecification();
    }

    [RowTest]
    [Row("[email protected]")]
    [Row("[email protected]")]
    public void ShouldBeSatisfied(String email)
    {
        Assert.That(SUT.IsSatisfiedBy(email));
    }

    [RowTest]
    [Row("one_two.com")]
    [Row("one_two@")]
    public void ShouldNotBeSatisfied(String email)
    {
        Assert.That(SUT.IsSatisfiedBy(email), Is.False);
    }
}

 

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:

[Subject(typeof(EmailSpecification), "is satisfied")]
public class when_validating_an_email_address_with_a_number_in_the_local_part
    : email_specification_specs
{
    Establish context = () =>
        EmailAddress = "[email protected]";

    Behaves_like<SatisfiedSpecificationBehavior> a_satisfied_specification;
}

[Subject(typeof(EmailSpecification), "is satisfied")]
public class when_validating_an_email_address_with_a_number_in_the_domain_name
    : email_specification_specs
{
    Establish context = () =>
        EmailAddress = "[email protected]";

    Behaves_like<SatisfiedSpecificationBehavior> a_satisfied_specification;
}

[Subject(typeof(EmailSpecification), "is satisfied")]
public class when_validating_an_email_address_without_an_At_sign
    : email_specification_specs
{
    Establish context = () =>
        EmailAddress = "one_two.com";

    Behaves_like<UnsatisfiedSpecificationBehavior> an_unsatisfied_specification;
}

[Subject(typeof(EmailSpecification), "is satisfied")]
public class when_validating_an_email_address_without_a_domain
    : email_specification_specs
{
    Establish context = () =>
        EmailAddress = "one_two@";

    Behaves_like<UnsatisfiedSpecificationBehavior> an_unsatisfied_specification;
}

 

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.

public abstract class email_specification_specs
{
    Establish context = () =>
    {
        SUT = new EmailSpecification();
    };

    Because of = () =>
        Result = SUT.IsSatisfiedBy(EmailAddress);

    protected static Boolean Result;
    protected static String EmailAddress { get; set; }
    protected static EmailSpecification SUT { get; set; }
}

 

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:

[Behaviors]
public class SatisfiedSpecificationBehavior
{
    protected static Boolean Result;
    
    It should_satisfy_the_specification = () =>
        Result.ShouldBeTrue();
}

[Behaviors]
public class UnsatisfiedSpecificationBehavior
{
    protected static Boolean Result;

    It should_not_satisfy_the_specification = () =>
        Result.ShouldBeFalse();
}

 

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.  

2 thoughts on “Behaviors with MSpec

  1. It’s neat and all, but I’m not sure what this brings to the table but more complexity…and code.

    My code base is complex enough. I don’t need to train the team to learn a cryptic way of testing.

    Cool but I’ll pass. Thanks. 😉

  2. Same here, I don’t see how this makes my life as a developer easier. I would rather just use a base class to put the general code in and use it for a new spec. Which I think is more transparent and less hasle.

Comments are closed.

Proudly powered by WordPress | Theme: Code Blog by Crimson Themes.