Builder Pattern for Unit Tests with C#

2022, May 24

The Builder pattern is a creational design pattern, which allows the construction of complex objects step by step. This pattern provides an elegant way to create Test Data to be used in Unit Testing.

Without the pattern

Test Data structures become rigid datasets, and the more you use them, the more repeated Test Data structures you get, and with that, you break the DRY principle (don't repeat yourself)1.

Imagine creating multiple structures like that on Unit Tests:

[Fact]
public async Task GivenUserDto_WhenBmiIsWithinRange_ShouldDoNothing()
{
    // Arrange
    _userDto = new UserDto() {
        FirstName = "John",
        Lastname = "Smith",
        DoB = "1900-01-01",
        Height = 180d, //cm (double)
        Weight = 80d //kg (double)
    }

    // Act
    ...

    // Assert
    ...
}

Depending on the number of unit tests, this would definitely increase the number of lines unnecessarily and repeatedly.

With the pattern

The pattern would simplify the number of lines and would provide an intelligible way for our Unit tests, giving significance to the Test.

[Fact]
public async Task GivenUserDto_WhenBmiIsWithinRange_ShouldDoNothing()
{
    // Arrange
    _userDto = new UserDtoBuilder()
        .WithFixture() // However you decide to populate the fields automatically
        .WithHeight(180d)
        .WithWeight(80d)
        .Build();

    // Act
    ...

    // Assert
    ...
}

The focus then becomes on how you manipulate the Test Data to validate a Business Domain rule, and you also don't break the encapsulation principle (if that is applicable to your model).

This can be easily achieved with a simple implementation like that:

public class UserDtoBuilder
{
    private UserDto _userDto = new();

    public UserDtoBuilder WithHeight(double height)
    {
        _userDto.Height = height;
        return this;
    }

    public UserDtoBuilder WithWeight(double weight)
    {
        _userDto.Weight = weight;
        return this;
    }

    public UserDtoBuilder WithFixture()
    {
        var fixture = new Fixture(); // have you used AutoFixture before?
        _userDto = fixture.Create<UserDto>();
        return this;
    }

    public UserDto Build()
    {
        return _userDto;
    }
}