A Reusable Builder Class for .NET testing
- by Liam McLennan
When writing tests, other than end-to-end integration tests, we often need to construct test data objects. Of course this can be done using the class’s constructor and manually configuring the object, but to get many objects into a valid state soon becomes a large percentage of the testing effort. After many years of painstakingly creating builders for each of my domain objects I have finally become lazy enough to bother to write a generic, reusable builder class for .NET. To use it you instantiate a instance of the builder and configuring it with a builder method for each class you wish it to be able to build. The builder method should require no parameters and should return a new instance of the type in a default, valid state. In other words the builder method should be a Func<TypeToBeBuilt>. The best way to make this clear is with an example. In my application I have the following domain classes that I want to be able to use in my tests: public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsAndroid { get; set; }
}
public class Building
{
public string Street { get; set; }
public Person Manager { get; set; }
}
The builder for this domain is created like so:
build = new Builder();
build.Configure(new Dictionary<Type, Func<object>>
{
{typeof(Building), () => new Building {Street = "Queen St", Manager = build.A<Person>()}},
{typeof(Person), () => new Person {Name = "Eugene", Age = 21}}
});
Note how Building depends on Person, even though the person builder method is not defined yet. Now in a test I can retrieve a valid object from the builder:
var person = build.A<Person>();
If I need a class in a customised state I can supply an Action<TypeToBeBuilt> to mutate the object post construction:
var person = build.A<Person>(p => p.Age = 99);
The power and efficiency of this approach becomes apparent when your tests require larger and more complex objects than Person and Building. When I get some time I intend to implement the same functionality in Javascript and Ruby. Here is the full source of the Builder class:
public class Builder
{
private Dictionary<Type, Func<object>> defaults;
public void Configure(Dictionary<Type, Func<object>> defaults)
{
this.defaults = defaults;
}
public T A<T>()
{
if (!defaults.ContainsKey(typeof(T))) throw new ArgumentException("No object of type " + typeof(T).Name + " has been configured with the builder.");
T o = (T)defaults[typeof(T)]();
return o;
}
public T A<T>(Action<T> customisation)
{
T o = A<T>();
customisation(o);
return o;
}
}