# Boosting the Builder Pattern Using Bogus

> Bogus and the Builder pattern join forces to simplify test data generation and improve the quality of your test suite.

- URL: https://blog.nimblepros.com/blogs/boosting-the-builder-pattern-using-bogus/
- Author: [Kyle McMaster](https://blog.nimblepros.com/authors/kyle/)
- Published: 2024-02-14
- Series: [Testing Techniques](https://blog.nimblepros.com/series/testing-techniques/)
- Categories: [Software Development](https://blog.nimblepros.com/categories/software-development/)
- Publisher: [NimblePros](https://blog.nimblepros.com/)

---

*This article is part of a series on leveling up your testing skills*
- [Creating Test Objects via Design Patterns](https://blog.nimblepros.com/blogs/creating-test-objects-via-design-patterns/)
- [Supercharging Your Test Data With AutoFixture](https://blog.nimblepros.com/blogs/supercharging-your-test-data-with-autofixture/)
- [Creating Domain-Driven Test Data With Bogus](https://blog.nimblepros.com/blogs/creating-domain-driven-test-data-with-bogus)
- [Boosting the Builder Pattern Using Bogus](https://blog.nimblepros.com/blogs/boosting-the-builder-pattern-using-bogus)

## Introduction 👋

In this entry in the series on leveling up your testing skills, we'll take a look at a combination of patterns that I have been using that combines some of the lessons learned in part 1 and part 3. First, we'll revisit the Builder pattern to create test data for a [Data Transfer Object](https://ardalis.com/web-api-dto-considerations/). Then, we'll see how we can improve that builder to use Bogus to simplify the boilerplate involved and improve the quality of the test data created.

For our sample domain, we'll continue using the `Contributor` entity that we've used throughout this series. In this case, however, we have a `ContributorDTO` that represents an object used by a `ContributorService` to send a message to a queue or push data to an external system. In practice, you might not always be able to negotiate the contract of the data with the external system. This can lead to some differences between your domain model and the contract DTO. In this case, the `ContributorDTO` has a `Status` property that is a string, but the `Contributor` entity has a `Status` property that is a `ContributorStatus` enum. The `ContributorDTO` also has a few additional properties that are not present in the `Contributor` entity but may be useful in the context of the external system. For the sake of our example, we'll just assume that either our application is capable of producing these values or that they may be optional in the external system. The reason they are present is to demonstrate how useful the Builder pattern and Bogus can be when generating objects with a large amount of properties.

```csharp
public class Contributor(
  string email,
  string firstName,
  string lastName,
  int followers,
  int following,
  int stars,
  ContributorStatus status) : EntityBase, IAggregateRoot
{
  public string Email { get; internal set; } = Guard.Against.NullOrEmpty(email);
  public string FirstName { get; set; } = Guard.Against.NullOrEmpty(firstName);
  public string LastName { get; internal set; } = Guard.Against.NullOrEmpty(lastName);
  public int Followers { get; internal set; } = Guard.Against.Negative(followers);
  public int Following { get; internal set; } = Guard.Against.Negative(following);
  public int Stars { get; internal set; } = Guard.Against.Negative(stars);
  public ContributorStatus Status { get; internal set; } = Guard.Against.Null(status);
}

public class ContributorStatus : SmartEnum<ContributorStatus>
{
  public static readonly ContributorStatus CoreTeam = new(nameof(CoreTeam), 1);
  public static readonly ContributorStatus Community = new(nameof(Community), 2);
  public static readonly ContributorStatus NotSet = new(nameof(NotSet), 3);

  protected ContributorStatus(string name, int value) : base(name, value) { }
}

public record ContributorDTO(
  int Id,
  string Email,
  string FirstName,
  string LastName,
  int Followers,
  int Following,
  int Stars,
  string Status,
  DateTimeOffset? CreatedOn,
  DateTimeOffset? ModifiedOn,
  int? ContributionCount,
  int? NumberOfDaysInStreak,
  int? LongestStreakEver,
  int? MostContributionsInOneDay);
```

Next, we have a `ContributorService` that has a method `SendSomeMessage`. This is the service that will be the focus of our tests. For the sake of this example, `SendSomeMessage` will simply validate that the `Id` property of `ContributorDTO` is not negative or zero and return a completed task. This validation gives us the ability to write at least 2 tests to assert the success and failure paths of that validation. In a real-world scenario, we'd expect that this method would then go on to send a message to a queue or service bus.

```csharp
public class ContributorService
{
  /// <summary>
  /// Should send a message to a queue or some endpoint, but for now just validates contributorDTO.Id and returns a completed task
  /// </summary>
  /// <param name="contributorDTO"></param>
  /// <returns></returns>
  public Task SendSomeMessage(ContributorDTO contributorDTO)
  {
    Guard.Against.NegativeOrZero(contributorDTO.Id, nameof(contributorDTO.Id));
    return Task.CompletedTask;
  }
}
```

## Builder Pattern Refresh 🔍

The following two tests demonstrate how we can use the Builder pattern to create a `ContributorDTO` with a valid `Id` and an invalid `Id`. The `ContributorDtoBuilder` in this iteration implements the conventional builder pattern with private fields for test values, "WithSomeProperty" methods for each customizable property, and a `Build` method to instantiate the output type (in this case `ContributorDTO`). If you need a refresher on the Builder pattern, check out part 1 of this series [Creating Test Objects via Design Patterns](https://blog.nimblepros.com/blogs/creating-test-objects-via-design-patterns/). Once we've instantiated our `ContributorDTO` with the `ContributorDtoBuilder`, we can then instantiate the `ContributorService` or System Under Test (SUT) and assert that the `SendSomeMessage` method behaves as expected.

```csharp
public class SendSomeMessage
{
  [Fact]
  public void ShouldNotThrowExceptionWhenContributorDTOIsPopulated()
  {
    var contributorDto = new ContributorDtoBuilder()
      .WithTestValues()
      .WithStatus(ContributorStatus.CoreTeam)
      .Build();
    var sut = new ContributorService();

    Action act = () => sut.SendSomeMessage(contributorDto);

    act.Should().NotThrow<Exception>();
  }

  [Theory]
  [InlineData(-1)]
  [InlineData(0)]
  public void ShouldThrowAnExceptionWhenDtoFailsValidation(int id)
  {
    var contributorDto = new ContributorDtoBuilder()
      .WithTestValues()
      .WithId(id)
      .WithStatus(ContributorStatus.CoreTeam)
      .Build();
    var sut = new ContributorService();

    Action act = () => sut.SendSomeMessage(contributorDto);

    act.Should().Throw<Exception>();
  }
}
```

```csharp
public class ContributorDtoBuilder
{
  private int _id;
  private string _email;
  private string _firstName;
  private string _lastName;
  private int _followers;
  private int _following;
  private int _stars;
  private string _status;
  private DateTimeOffset? _createdOn;
  private DateTimeOffset? _modifiedOn;
  private int? _contributionCount;
  private int? _numberOfDaysInStreak;
  private int? _longestStreakEver;
  private int? _mostContributionsInOneDay;

  public ContributorDtoBuilder WithTestValues()
  {
    _id = 1;
    _email = "test@email.com";
    _firstName = "Test";
    _lastName = "User";
    _followers = 1;
    _following = 1;
    _stars = 1;
    _status = ContributorStatus.NotSet.Name;
    _createdOn = DateTimeOffset.UtcNow;
    _modifiedOn = DateTimeOffset.UtcNow;
    _contributionCount = 1;
    _numberOfDaysInStreak = 1;
    _longestStreakEver = 1;
    _mostContributionsInOneDay = 1;
    return this;
  }

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

  // ...other "WithSomeProperty" methods excluded

  public ContributorDTO Build()
  {
    return new(
      Id: _id,
      Email: _email,
      FirstName: _firstName,
      LastName: _lastName,
      Followers: _followers,
      Following: _following,
      Stars: _stars,
      Status: _status,
      CreatedOn: _createdOn,
      ModifiedOn: _modifiedOn,
      ContributionCount: _contributionCount,
      NumberOfDaysInStreak: _numberOfDaysInStreak,
      LongestStreakEver: _longestStreakEver,
      MostContributionsInOneDay: _mostContributionsInOneDay);
  }
}
```

One thing to note about the `ContributorDtoBuilder` is that it does save quite a bit of repeated code in our tests. But it does come with a bit of overhead as we need to create a field for each property of the `ContributorDTO` and "WithSomeProperty" setter methods if the tests are dependent on arranging those fields. This can be a bit of a pain to maintain, especially if the `ContributorDTO` has a large number of properties or changes often.

## It's Bogus Time! 🎉

We'll skip the introduction to Bogus and its many benefits. If you missed that article, check out part 3 of this series [Creating Domain-Driven Test Data With Bogus](https://blog.nimblepros.com/blogs/creating-domain-driven-test-data-with-bogus). Combining Bogus with the Builder pattern has many benefits. We can simplify the Builder implementation, provide greater flexibility over strongly typed Bogus classes, and improve the quality of the test data for our test suite. This can be a powerful refactoring in test suites with many Builders that generate large amounts of real-world or user-driven test data which can be tedious for developers to creatively come up with. 

The following is the updated `ContributorDtoBuilder` that uses Bogus to generate the `ContributorDTO` test data. The `WithTestValues` method has been removed along with most of the private fields. We can still utilize the "WithSomeProperty" arrangement convention if we want to set test-specific values. This behavior is easily preserved by simply passing the field to the `Faker` or null coalescing the field and having the `Faker` generate a value as seen with the `Id` and `Status` fields below.

```csharp
public class ContributorDtoBuilder
{
  private int? _id;
  private ContributorStatus? _status;

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

  public ContributorDtoBuilder WithStatus(ContributorStatus status)
  {
    _status = status;
    return this;
  }

  public ContributorDTO Build()
  {
    var faker = new Faker<ContributorDTO>()
      .CustomInstantiator(f => new ContributorDTO(
        Id: _id ?? f.UniqueIndex, // <-- Using a private field or generating unique index if not set
        Email: f.Person.Email,
        FirstName: f.Person.FirstName,
        LastName: f.Person.LastName,
        Followers: f.Random.Int(1, 100),
        Following: f.Random.Int(1, 100),
        Stars: f.Random.Int(1, 100),
        Status: _status?.Name ?? f.PickRandom<ContributorStatus>(ContributorStatus.List).Name,
        CreatedOn: f.Date.Past(),
        ModifiedOn: f.Date.Past(),
        ContributionCount: f.Random.Int(100, 1000),
        NumberOfDaysInStreak: f.Random.Int(7, 10),
        LongestStreakEver: f.Random.Int(11, 100),
        MostContributionsInOneDay: f.Random.Int(10, 20)));

    return faker.Generate();
  }
}
```

It's also important to note that in most implementations using this style of Builder, the changes discussed in this article shouldn't be a large breaking change as any existing arrangement of values would likely be using "WithSomeProperty" methods. If you have a large test suite or many builders, you may want to consider a gradual migration which thankfully is quite easy to do since this approach is incrementally adoptable for most tests. 😉

In summary, we revisited the Builder pattern and saw how we could refactor an existing builder with the Bogus library for an improved test data generation experience. We saw how the Bogus library can simplify the Builder pattern implementation while still providing the flexibility to arrange values per test as needed. Lastly, we discussed how the combination of these practices can be adopted incrementally in a large code base.

## Resources 📖

- [Sample Code](https://github.com/KyleMcMaster/BogusBuilder)
- [Builder Pattern Motivation](https://ardalis.com/improve-tests-with-the-builder-pattern-for-test-data)
- [Bogus Repository](https://github.com/bchavez/Bogus)
- [Sample Domain Inspiration](https://github.com/ardalis/CleanArchitecture)
