HOW TO CREATE A BUILDER FOR YOUR UNIT TESTS
A builder is a class that creates another class for you, using a fluent API.
Person me = new PersonBuilder().WithFirstName(“Peter”).Build();The test builder is used in unit tests to:
* Make the code less brittle. We isolate the construction of the objects used in the tests, and only do it in one place, so when we add a new parameter in the future, we only have to change the test code in one place.
* Makes the job of writing tests easier, we write less code because we only call a function that creates the instance, instead of writing the entire construction with all the parameters in every test.
* Make the code DRY (Don’t Repeat Yourself). We only do the construction, the newing up, in one place.
The code
public class PersonBuilder
{
private readonly Person _item = new Person();
public PersonBuilder()
{
_item.FirstName = "FirstName";
_item.LastName = "LastName";
}
public Person Build() => _item;
public List<Person> ToList() => new List<Person> { _item };
public PersonBuilder WithFirstName(string newFirstName)
{
_item.FirstName = newFirstName;
return this;
}
public PersonBuilder WithLastName(string newLastName)
{
_item.LastName = newLastName;
return this;
}
}
The Code explained
Build()
The first thing we do is to create a default object. It is the _item in this example. This is the bare minimum in a builder, the foundation. Because when we have this, we can call the Build() function.
This means that we have decoupled the creation of the object from the tests. Which is a good thing, because now, the tests doesn’t have to concern themselves with the details of how the object is created.
And also, if we create the object in several places, we don’t have to write all the code needed to initiate it with test data. It is done for us.
This also means that when we change the Person class, for instance if we want to add a new property for the title, like Ms or Doctor, we only do this in one place. And the tests just get the changes for free.
WithFirstName()
Now, when we write tests, we want control over the data that our test objects carry. So we add With…() functions. They open up for you to override the default data. And it does it with a fluent API.
So, whenever a test needs a special kind of data, you write like this:
var testPerson =
new PersonBuilder()
.WithFirstName(“Jane”)
.Build();
And you get the person with the properties
FirstName = “Jane”
LastName = “LastName”
ToList()
Many times we need a list with the test data. This function solves that for us.
List<Person> persons = new PersonBuilder().ToList();
And voila, a list with a person. Of course, this works equally well with the With…() functions too.
Global test project
The objects are often used in several projects. And if you have one test project per production project, you need to use the builders in all of them. So don’t put them in any of the test projects. That could lead you to do duplicate builders, one in each test project doing the exact same thing. Not dry. I’ve seen it happen.
Instead, create a “GlobalTest” project, one that all the other test projects can reference. And put them there.
Things to remember
This is only used in tests. Don’t be tempted to use them in production. Test code will contain things you don’t want in production code. Like the default data.
This is test code. Don’t be tempted to use production code rules on it.
Like verifying that the data is not null. Someday you want to add null in the tests data to verify how the code handles that.
Like removing dead code. This is a tool. It sits in your toolbox, ready to be used whenever you need it. Leave the unused ToList() where it is. It doesn’t hurt anybody. But it will keep the flow when you write your tests and just call it, instead of breaking your concentration to go into the builder and add it when you need it.
If you want to read more about the tools for unit testing, have a look here:
developerkingdom.se/test-and-quality/
Or if you’d like to know more about the cousin, Fixtures:
developerkingdom.se/unit-test/how-to-create-a-test-fixture/
— Cheers!