When creating tests (for TDD or BDD), use fluent builders

No, I don’t mean the kind that come around and drink all your coffee / tea. When you are testing your code you often have to initialise various classes beforehand. There are several ways to do this. Consider this simple api that takes in a ‘Booking’ object and returns the cost of the booking.

public int GetCostOfBooking(Booking booking)
{
    // ... implementation not important here
}

Where the Booking object is

public class Booking
{
    public DateTime FirstDay { get; set; }

    public DateTime LastDay { get; set; }

    public decimal LocationCostPerNight { get; set; }

    public Customer Customer { get; set; }
}

and the Customer object is

public class Customer
{
    public decimal PercentDiscount { get; set; }
}

Your test could be set up as follows

[TestMethod]
public void BookingCostIsCorrect()
{
    var customer = new Customer { PercentDiscount = 10 };
    var booking = new Booking
                      {
                          FirstDay = DateTime.Parse("2015/08/15"), 
                          LastDay = DateTime.Parse("2015/08/20"), 
                          LocationCostPerNight = 10, 
                          Customer = customer
                      };
    var actual = GetCostOfBooking(booking);
    var expected = 45;
    Assert.AreEqual(expected, actual);
}

(please bear with me on these primitive samples and tests!)

Now this doesn’t look too bad, but what if I add more properties to Customer and Booking or force the use of constructors and specific set methods?

public class Booking
{
    public DateTime FirstDay { get; private set; }
    public DateTime LastDay { get; private set; }
    public decimal LocationCostPerDay { get; private set; }
    public Customer Customer { get; private set; }

    public void SetCostPerDay(decimal costPerDay)
    {
        this.LocationCostPerDay = costPerDay;
    }

    public void SetFirstDay(DateTime firstDay)
    {
        this.FirstDay = firstDay;
    }

    public void SetLastDay(DateTime lastDay)
    {
        this.LastDay = lastDay;
    }

    public void SetCustomer(Customer customer)
    {
        this.Customer = customer;
    }
}
public class Customer
{
    public Customer(string firstName, string lastName, DiscountScheme discountScheme)
    {
        this.FirstName = firstName;
        this.LastName = LastName;
        this.DiscountScheme = discountScheme;
    }

    public string FirstName { get; private set; }

    public string LastName { get; private set; }

    public DiscountScheme DiscountScheme { get; private set; }
}
public class DiscountScheme
{
    public decimal PercentDiscount { get; private set; }

    public void SetDiscount(decimal discount)
    {
        this.PercentDiscount = discount;
    }
}

Now my test setup becomes

[TestMethod]
public void BookingCostIsCorrect()
{
     var discountScheme = new DiscountScheme();
     discountScheme.SetDiscount(10);
     var customer = new Customer("Johnny", "BigCheese", discountScheme);
     var booking = new Booking();
     booking.SetCustomer(customer);
     booking.SetFirstDay(DateTime.Parse("2015/08/15"));
     booking.SetLastDay(DateTime.Parse("2015/08/15"));
     booking.SetCostPerDay(10);
     var actual = GetCostOfBooking(booking);
     var expected = 45;
     Assert.AreEqual(expected, actual);
}

Oh, not so nice any more. I have polluted my test with superfluous information, I am relying on calling the right methods to initialise properties and I have made the test that bit more obfuscated.

Instead, how about this

[TestMethod]
public void BookingCostIsCorrect()
{
    var customer = new CustomerBuilder()
                        .WithDiscount(10);

    var booking = new BookingBuilder()
                        .FromFirstDay("2015/08/15")
                        .ToLastDay("2015/08/19")
                        .ForCustomer(customer)
                        .AtCostPerDay(10);

    var actual = GetCostOfBooking(booking);
    var expected = 45;
    Assert.AreEqual(expected, actual);
}

Much nicer! It is easy to see what we are testing and how it is set up. There is no unnecessary information here. Also, thanks to the use of a builder, all the code for creating those objects is in a central place so you don’t have to edit all your tests when there is a change in how they should be initialised.

Great!  How do I do it?

It’s really not that difficult and once you have written a few you start to get the hang of it.  For instance here are the builder classes we have used above

public class BookingBuilder
{
    private Booking booking;

    public BookingBuilder()
    {
        this.booking = new Booking();
    }

    public static implicit operator Booking(BookingBuilder builder)
    {
        return builder.booking;
    }

    public BookingBuilder ForCustomer(Customer customer)
    {
        this.booking.SetCustomer(customer);
        return this;
    }

    public BookingBuilder FromFirstDay(string firstDay)
    {
        this.booking.SetFirstDay(DateTime.Parse(firstDay));
        return this;
    }

    public BookingBuilder ToLastDay(string lastDay)
    {
        this.booking.SetLastDay(DateTime.Parse(lastDay));
        return this;
    }

    public BookingBuilder AtCostPerDay(decimal costPerDay)
    {
        this.booking.SetCostPerDay(costPerDay);
        return this;
    }
}
public class CustomerBuilder
{
    private Customer customer;

    public CustomerBuilder()
    {
        this.customer = new Customer("Test", "Test", new DiscountScheme());
    }

    public static implicit operator Customer(CustomerBuilder builder)
    {
        return builder.customer;
    }

    public CustomerBuilder WithDiscount(decimal discount)
    {
        this.customer.DiscountScheme.SetDiscount(discount);
        return this;
    }
}

The main thing to remember when creating a fluent builder is that your methods return the class itself, that allows you to chain them. Note also the static implicit operator that converts between the builder and the object it is building. Adding that means we can directly use the builder in place of the object being built (ie passing the booking builder into the GetCostOfBooking method.

I mean this blog to be an introduction to fluent builders – you can get much more fancy with them than this and I have written a blog here that shows how to do that.

If you have any questions or want to add your own feedback, please comment below.  We would love to hear from you.

SHARE IT:

Commenting area

  1. Fergal Coffey 7th August 2015 at 8:38 am · · Reply

    Nice article; check out Autofixture which does exactly this without the need for custom builders https://github.com/AutoFixture/AutoFixture

    • Thanks Fergal.
      AutoFixture is excellent although I prefer the specificity that custom builders give you

      var booking = new BookingBuilder()
      .FromFirstDay("2015/08/15")
      .ToLastDay("2015/08/19")
      .ForCustomer(customer)
      .AtCostPerDay(10);

      Darren

Leave a Reply