Test Data Builder Pattern
When unit testing we often need to fill any POCO objects with enough data to satisfy the needs of the test. Redoing this in every test can quickly become a burden and slow down the testing process. Test Data Builder is a pattern that’s meant to help in just that situation.
With test data builder you build a fluent interface that allows you to build out your POCO objects by only replacing the properties that you need specific values for. The other values are set to defaults specified by the builder.
Here’s a classic example of test data builder:
Our POCO Object
1: Public Class Person
2: Public Property FirstName As String
3: Public Property LastName As String
4: Public Property DateOfBirth As DateTime
5: End Class
Our Test Data Builder Class
1: Public Class PersonBuilder
2: 'Set the defaults
3: Private _firstName As String = "Default"
4: Private _lastName As String = "Default"
5: Private _dateOfBirth As DateTime = New DateTime(1999, 12, 31)
6:
7: Public Function WithFirstName(ByVal firstName As String) As PersonBuilder
8: _firstName = firstName
9: Return Me
10: End Function
11:
12: Public Function WithLastName(ByVal lastName As String) As PersonBuilder
13: _lastName = lastName
14: Return Me
15: End Function
16:
17: Public Function WithDateOfBirth(ByVal dateOfBirth As DateTime) As PersonBuilder
18: _dateOfBirth = dateOfBirth
19: Return Me
20: End Function
21:
22: Public Function Build() As Person
23: Return New Person() With
24: {
25: .FirstName = _firstName,
26: .LastName = _lastName,
27: .DateOfBirth = _dateOfBirth
28: }
29: End Function
30: End Class
Usage example
1: Dim person = New PersonBuilder() _
2: .WithFirstName("Stacy") _
3: .WithDateOfBirth(New DateTime(1984, 12, 6)) _
4: .Build()
Still a lot of work
Depending on how many objects you need to create builders for and how many properties each object has you can still be in for a lot of upfront work following the test data builder pattern to create that fluent experience.
Creating the builder pattern with object initializers
In .NET we have object initializers that can take much of this extra work out by providing an expressive method of setting the object properties. If possible, simply subclassing off of the POCO will prevent us from even needing to write out all of the properties, then we can set default values in the constructor.
Our Subclassed Test Data Builder
1: Public Class PersonBuilderWithObjectInitializers
2: Inherits Person
3:
4: Public Sub New()
5: FirstName = "Default"
6: LastName = "Default"
7: DateOfBirth = New DateTime(1999, 12, 31)
8: End Sub
9:
10: Public Function Build() As Person
11: Return New Person() With
12: {
13: .FirstName = FirstName,
14: .LastName = LastName,
15: .DateOfBirth = DateOfBirth
16: }
17: End Function
18: End Class
The new builder’s usage
1: Dim person = New PersonBuilderWithObjectInitializers() With
2: {
3: .FirstName = "Stacy",
4: .DateOfBirth = New DateTime(1984, 12, 6)
5: }.Build()
Why still have a build method?
Some of you might be asking, “Why keep the build method when polymorphism should do the work?” Well, good point. The reason I keep the build method is that when working against Entity Framework against POCO objects it will attempt to figure out the true type of the object, not just the type the object is cast as. As a result, it will complain when trying to persist the object. The build method in this case just acts as added assurance that if any type discovery is called against the object you will be safe knowing that the correct type will be discovered.
C# examples
Our POCO Object
1: public class Person
2: {
3: public string FirstName { get; set; }
4: public string LastName { get; set; }
5: public DateTime DateOfBirth { get; set; }
6: }
Our Test Data Builder Class
1: public class PersonBuilder
2: {
3: // Set our defaults
4: private string _firstName = "Default";
5: private string _lastName = "Default";
6: private DateTime _dateOfBirth = new DateTime(1999, 12, 31);
7:
8: public PersonBuilder WithFirstName(string firstName)
9: {
10: _firstName = firstName;
11: return this;
12: }
13:
14: public PersonBuilder WithLastName(string lastName)
15: {
16: _lastName = lastName;
17: return this;
18: }
19:
20: public PersonBuilder WithDateOfBirth(DateTime dateOfBirth)
21: {
22: _dateOfBirth = dateOfBirth;
23: return this;
24: }
25:
26: public Person Build()
27: {
28: return new Person
29: {
30: FirstName = _firstName,
31: LastName = _lastName,
32: DateOfBirth = _dateOfBirth
33: };
34: }
35: }
Usage example
1: //arrange
2: var person = new PersonBuilder()
3: .WithFirstName("Stacy")
4: .WithDateOfBirth(new DateTime(1984, 12, 6))
5: .Build();
Our Subclassed Test Data Builder
1: public class PersonBuilderWithObjectInitializers : Person
2: {
3: public PersonBuilderWithObjectInitializers()
4: {
5: // Set our defaults
6: FirstName = "Default";
7: LastName = "Default";
8: DateOfBirth = new DateTime(1999, 12, 31);
9: }
10:
11: public Person Build()
12: {
13: return new Person
14: {
15: FirstName = FirstName,
16: LastName = LastName,
17: DateOfBirth = DateOfBirth
18: };
19: }
20: }
The new builder’s usage
1: var person = new PersonBuilderWithObjectInitializers
2: {
3: FirstName = "Stacy",
4: DateOfBirth = new DateTime(1984, 12, 6)
5: }.Build();