Using The Builder Pattern to improve Testability and Readability

Using The Builder Pattern to improve Testability and Readability

Sometimes setting things up can get a bit messy, especially when we want to test coupled code with a lot of dependencies. This is where the Builder Pattern comes in handy - it helps us create objects in an easier and cleaner way, especially when they are a bit complex to set up.

What's the Builder Pattern All About?

In simple terms, the Builder Pattern is a tool that helps us create objects in a neat and flexible way. It lets us create an object step-by-step, and not worry about how the underlying object is put together while we’re doing it.

Example: The "Complex" Order Class

Imagine we have a class named Order with a few fields like id, shipment, payment, and so on.

public class Order
{
    public int Id { get; set; }
    public Shipment Shipment { get; set; }
    public Payment Payment { get; set; }
    // ... Maybe some more properties
}

Sometimes, for various reasons, these fields might need a bit of setting up and we can’t leave them empty.

Introducing the Builder Pattern

With the Builder Pattern, we can create an Order while keeping the setup simple and focusing on the main thing we’re testing.

1. Creating a Builder:
We create a new class in our test project called OrderBuilder and add the properties we need to it.

public class OrderBuilder
{
    private int _id;
    private Shipment _shipment;
    private Payment _payment;

    public OrderBuilder WithId(int id)
    {
        _id = id;
        return this;
    }

    public OrderBuilder WithShipment(Shipment shipment)
    {
        _shipment = shipment;
        return this;
    }

    public OrderBuilder WithPayment(Payment payment)
    {
        _payment = payment;
        return this;
    }

    public Order Build()
    {
        return new Order
        {
            Id = _id,
            Shipment = _shipment ?? new Shipment(),
            Payment = _payment ?? new Payment()
        };
    }
}

This coding-style, where we can chain calls together, is known as Fluent and makes our code really easy to read.

Note: In this example neither Shipment or Payment was especially complex to set up, so for the sake of keeping this example short lets just imagine that they are much more complicated to set up.


2. Using the Builder:
We can use this builder to make an Order object in a super readable way like this.

var order = new OrderBuilder().WithId(1).WithShipment(new Shipment()).Build();

This way, we can quickly see what's important for the test, and if we have to change the setup, we only have to do it in the builder class.

Example: In Action!

Here’s a quick test example using NUnit to illustrate:

[Test]
public void When_ShippingIsExpress_DeliveryDateIsTomorrow()
{
    // Arrange
    var order = new OrderBuilder()
                .WithId(1)
                .WithShipment(new Shipment { Type = "Express" })
                .Build();
                
    // Act
    var deliveryDate = order.GetEstimatedDeliveryDate();
    
    // Assert
    Assert.AreEqual(DateTime.Today.AddDays(1), deliveryDate);
}

In the test above, we're checking that if we choose "Express" shipping, our estimated delivery date is set to tomorrow. And even though our Order object has a dependency and requires a Payment set up we can leave that out of our test since this is not important for our current scope. Thanks to the builder, it’s easy to set up our Order and keep our test easy to read and understand!

Conclusion:

In the world of coding and testing, simplicity and clarity are key. The Builder Pattern serves as a trusty sidekick, making the process of setting up and testing our objects straightforward and clean. By allowing us to focus on the essential parts of our tests and keeping the messy setup details tucked away, we can write tests that are not only effective but also a breeze to read and maintain. Whether you’re diving into testing for the first time or looking to refine your approach, give the Builder Pattern a try. 

Cheers friends! ❤️