Builder Pattern in C# with TDD: Step-by-Step Guide
Learn how to implement the Builder Pattern in C# using TDD. Follow a Red-Green-Refactor approach to create modular, testable code structures

Find all the code in this article and more on GitHub at CodeCraft-Dispatch/design-patterns.
The Builder Pattern is a powerful design pattern for constructing complex objects in a step-by-step manner. This approach is particularly useful in software architecture practices where separating object creation from usage improves maintainability and testability.
In this article, we’ll implement the Builder Pattern using a Test-Driven Development (TDD) approach, following the Red-Green-Refactor cycle. TDD allows us to build our HouseBuilder class incrementally while ensuring that each functionality meets our requirements.
Red-Green-Refactor: The TDD Process
Red: Write a failing test for the functionality we want to add.
Green: Write the minimum code necessary to make the test pass.
Refactor: Improve the code structure and readability while keeping the tests green.
1. Red: Define the First Test
Let’s start by creating our HouseBuilderTests class. We’ll write a test for creating a house with a specified number of rooms. In TDD, we only define enough functionality to fail our initial tests.
using Xunit;
public class HouseBuilderTests
{
[Fact]
public void Build_ShouldReturnHouseWithSpecifiedRooms()
{
var builder = new HouseBuilder();
House house = builder.WithRooms(3).Build();
Assert.Equal(3, house.Rooms);
}
}In this test:
We’re using the
WithRoomsmethod to specify the number of rooms.We expect the
Buildmethod to return aHouseobject withRoomsset to 3.
At this point, if we run the test, it will not build. Creating the structure to allow the code to compile would be the first step. In C# the NotImplementException is a helpful default in this scenario. Running the test will fail (Red) because the classes are not implemented yet.
2. Green: Implement the Minimum Code
To pass the test, we’ll start by creating a simple House class with a Rooms property, along with a basic HouseBuilder class.
House.cs
public class House
{
public int Rooms { get; set; }
}IHouseBuilder.cs
public interface IHouseBuilder
{
IHouseBuilder WithRooms(int number);
House Build();
}HouseBuilder.cs
public class HouseBuilder : IHouseBuilder
{
private readonly House _house;
public HouseBuilder()
{
_house = new House();
}
public HouseBuilder WithRooms(int number)
{
_house.Rooms = number;
return this;
}
public House Build()
{
return _house;
}
}This code is enough to make the test pass. We now have a basic HouseBuilder class implementing the IHouseBuilder interface with methods WithRooms and Build, following the Green step by making the test pass with minimal code.
3. Refactor: Improve Code Structure
Since our initial implementation meets the test requirements, we can proceed to the Refactor phase. For now, our code is simple and doesn’t need major refactoring, so let’s move on to adding more functionality.
Going back to Red, we’ll write a failing test that continues to describe the design of our HouseBuilder.
Speeding up time to avoid the monotony of reading that, here is the end result:
HouseBuilderTests.cs
public class HouseBuilderTests
{
[Fact]
public void ShouldReturnHouseWithSpecifiedRooms()
{
var builder = new HouseBuilder();
House house = builder.WithRooms(3).Build();
Assert.Equal(3, house.Rooms);
}
[Fact]
public void ShouldReturnHouseWithSpecifiedDoors()
{
var builder = new HouseBuilder();
House house = builder.WithDoors(2).Build();
Assert.Equal(2, house.Doors);
}
[Fact]
public void ShouldReturnHouseWithSpecifiedWindows()
{
var builder = new HouseBuilder();
House house = builder.WithWindows(5).Build();
Assert.Equal(5, house.Windows);
}
[Fact]
public void ShouldReturnHouseWithGarage_WhenGarageIsSetToTrue()
{
var builder = new HouseBuilder();
House house = builder.WithGarage(true).Build();
Assert.True(house.HasGarage);
}
[Fact]
public void ShouldReturnHouseWithoutGarage_WhenGarageIsSetToFalse()
{
var builder = new HouseBuilder();
House house = builder.WithGarage(false).Build();
Assert.False(house.HasGarage);
}
[Fact]
public void ShouldReturnHouseWithAllSpecifiedProperties()
{
var builder = new HouseBuilder();
House house = builder.WithRooms(3)
.WithDoors(2)
.WithWindows(5)
.WithGarage(true)
.Build();
Assert.Equal(3, house.Rooms);
Assert.Equal(2, house.Doors);
Assert.Equal(5, house.Windows);
Assert.True(house.HasGarage);
}House.cs
public class House
{
public int Rooms { get; set; }
public int Doors { get; set; }
public int Windows { get; set; }
public bool HasGarage { get; set; }
}IHouseBuilder.cs
public interface IHouseBuilder
{
IHouseBuilder WithRooms(int number);
IHouseBuilder WithDoors(int number);
IHouseBuilder WithWindows(int number);
IHouseBuilder WithGarage(bool hasGarage);
House Build();
}HouseBuilder.cs
public class HouseBuilder : IHouseBuilder
{
private readonly House _house;
public HouseBuilder()
{
_house = new House();
}
public IHouseBuilder WithRooms(int number)
{
_house.Rooms = number;
return this;
}
public IHouseBuilder WithDoors(int number)
{
_house.Doors = number;
return this;
}
public IHouseBuilder WithWindows(int number)
{
_house.Windows = number;
return this;
}
public IHouseBuilder WithGarage(bool hasGarage)
{
_house.HasGarage = hasGarage;
return this;
}
public House Build()
{
return _house;
}
}At this point, our code is functional and all tests are passing. We can review the HouseBuilder class for readability and adherence to the Fluent Interface style. The builder methods are already chainable and consistent, so minimal refactoring is needed here.
By following a TDD approach, we ensured that each feature of the HouseBuilder class was guided by tests, resulting in code that:
Meets exact requirements: Each method has a clear purpose driven by tests.
Is robust and refactorable: Tests act as a safety net, allowing us to refactor without fear of breaking functionality.
Is readable and modular: The Fluent Interface style of the
HouseBuildermakes it clear and easy to use for constructing complexHouseobjects with various configurations.
Using the Builder Pattern with TDD offers an effective way to construct complex objects while maintaining code quality and readability. The TDD approach, especially the Red-Green-Refactor cycle, allows developers to incrementally build out functionality and focus on testable, maintainable code. The Builder Pattern combined with TDD provides a structured, testable approach to constructing entities with complex dependencies and configuration options.

