02 Jul 2016 Comment

These days, my default approach for writing tests is to construct the system under test using the builder pattern. Delegating construction of the SUT to a fluent builder has a number of benefits:

  1. Test code becomes more readable and expressive, encouraging meaningful tests, preventing setup code from drowning out test intention and ultimately providing better documentation of behaviour.
  2. Reuse of setup code is easier and the cost of adding or changing dependencies and construction logic is dramatically reduced.
  3. Semantically meaningful test setup encourages good encapsulation in production code (which, in turn, makes changing behaviour by adding and removing components - instead of ripping up tests - much easier).
  4. Dependencies’ validity can be tested in a single test and “assumed” in others by supplying valid defaults in the builder, further reducing noise in tests.

Test first

Let’s dive into some code. In this example, I want to build a price range filter that will take a collection of items, along with a minimum and maximum price, and filter out any items whose prices don’t fall within the range.

Starting with an initial test case…

[Test]
public void ItemsBelowMinimumPriceShouldBeFiltered()
{
    var items = new[] 
    {
        new Item { Price = 250 },
        new Item { Price = 40 },
        new Item { Price = 200 }
    };

    var sut = new SutBuilder()
        .WithMinimumPriceOf(200)
        .Build();

    var expected = new[] { items[0], items[2] };
    var actual = sut.Filter(items);

    Asser.That(actual, Is.EquivalentTo(expected));
}

The intention is clearly stated: given a minimum price of 200, the SUT should filter out the second item in items. The test is easily readable and not muddied by construction logic for the system under test itself.

The important principle is that the test does not care how the SUT is constructed; only how it behaves under the test conditions. As such, the construction of the SUT should always be expressed in the test as a semantic description of the scenario - how it is actually composed is an implementation detail.

If you build it…

Of course, that construction logic has to go somewhere. My preference is for that somewhere to be a nested class within the test fixture. With the test case written, ReSharper (or similar) can scaffold this with a few keystrokes.

private class SutBuilder
{
    private int _minimumPrice;

    public SutBuilder WithMinimumPriceOf(int price)
    {
        _minimumPrice = price;
        return this;
    }

    public PriceFilter Build()
    {
        return new PriceFilter(_minimumPrice);
    }
}

This encapsulates the actual construction of our SUT, cleanly decoupling it from the test itself (whose job is now to express our intention).

Hopefully, it’s easy to see at this stage how the same approach can be used to specify expectations for mocks and set up return values for stubs, while keeping the actual dependencies themselves neatly inside the builder.

Once again, code generation tools can assist in fleshing out the actual PriceFilter implementation, which we’ll do so our test goes green:

public class PriceFilter
{
    private int _minimumPrice;

    public PriceFilter(int minimumPrice)
    {
        _minimumPrice = minimumPrice;
    }

    public IEnumerable<Item> Filter(IEnumerable<Item> items)
    {
        return items.Where(o => o.Price >= _minimumPrice);
    }
}

Building up, building out

Great - we have a passing test and a working price filter. Now we want to add capability for a maximum price, so let’s write a test for that:

[Test]
public void ItemsAboveMaximumPriceShouldBeFiltered()
{
    var items = new[] 
    {
        new Item { Price = 500 },
        new Item { Price = 600},
        new Item { Price = 50 }
    };

    var sut = new SutBuilder()
        .WithMaximumPriceOf(500)
        .Build();

    var actual = sut.Filter(items);
    var expected = new[] { items[0], items[1] };

    Assert.That(actual, Is.EquivalentTo(expected));
}

And update our builder to support it:

private class SutBuilder
{
    private int _minimumPrice;
    private int _maximumPrice;

    public SutBuilder WithMinimumPriceOf(int price)
    {
        _minimumPrice = price;
        return this;
    }

    public SutBuilder WithMaximumPriceOf(int price)
    {
        _maximumPrice = price;
        return this;
    }

    public PriceFilter Build()
    {
        return new PriceFilter(_minimumPrice, _maximumPrice);
    }
}

And make things green again:

public class PriceFilter
{
    private int _minimumPrice;
    private int _maximumPrice;

    public PriceFilter(int minimumPrice, int maximumPrice)
    {
        _minimumPrice = minimumPrice;
        _maximumPrice = maximumPrice;
    }

    public IEnumerable<Item> Filter(IEnumerable<Item> items)
    {
        var result = items.Where(o => o.Price >= _minimumPrice);

        if (_maximumPrice > 0)
        {
            result = result.Where(o => o.Price <= _maximumPrice);
        }

        return result;
    }
}

Et voila: a working price filter and an expressive, easy-to-read test fixture, which facilitates easy reuse of setup code and, if necessary, allows us to change the construction or dependencies of our system under test with ease.