Skip to main content

Test Data Builders

The Test Data Builder pattern makes it easier to create input data or objects for unit tests

Why

Using Test Data Builders in your tests will:

  • Facilitate writing tests by allowing easy creation of inputs or expected data.
  • Facilitate test maintainability by decoupling the creation of objects in tests, and isolating it in a single location (Single Responsibility Principle)
  • Disseminate functional understanding in the team through the use of business terms
  • Facilitate the readability of the tests by explaining only the data implied in the use case

Problems

  • How to decouple object creation from implementation in your tests?
  • How to improve code readability in your tests?
  • How to speed up writing your test setup?

How to

The 4 rules of test data builders

  1. Have an instance variable for each constructor parameter
  2. Initialize its instance variables to commonly used or safe values
  3. Has a build method that creates a new object using the values in its instance variables
  4. Has chainable public methods for overriding the values in its instance variables

Examples

Considering the following command to create a property :

public class CreatePropertyCommand
{
public string Label { get; set; }
public bool? IsRanged { get; set; }
public bool? IsNumeric { get; set; }
public bool? IsMultiple { get; set; }
public IEnumerable<string> Choices { get; set; }
}

Without usage of test data builder

In each test on property creation, you will include quite the same command creation:

var command = new CreatePropertyCommand ("label", true, false, true, new string[] {1, 2});
  • Every time your object creation will change, you have to fix all tests the class is implied
  • What does the false value means?!?
  • What if the constructor arguments were not primitive types but required the creation of other objects?

With test data builders

public class CreatePropertyCommandBuilder
{
private string _label;
private bool _isMultiple = true;
private bool _isRanged = false;
private bool _isNumeric = true;
private string[] _choices = new string[0];

public static CreatePropertyCommandBuilder APropertyCreation
=> new CreatePropertyCommandBuilder();

public CreatePropertyCommandBuilder WithLabel(string label)
{
_label = label;
return this;
}

public CreatePropertyCommandBuilder WithChoices(string[] choices)
{
_choices = choices;
return this;
}

public CreatePropertyCommand Build()
{
return new CreatePropertyCommand(_label, _isMultiple, _isRanged, _isNumeric, _choices);
}
}

Using your builder in the tests

  • Decouple object construction from its implementation, reducing refactoring effort on changes
  • Explains data implied in the test case
//Arrange
var command = CreatePropertyCommandBuilder
.APropertyCreation
.WithLabel("Ma nouvelle propriété")
.Build();

// ...

// Assert
property.Label
.Should()
.Be("Ma nouvelle propriété");

Constraint

Replace redundant object creation by test data builders

Resources