On our current project we have decided to drop the ObjectMother pattern in favor of using TestDataBuilder.I picked up on TestDataBuilders from Colin’s blog, though I note that our own Matthew had a post on them on an older blog too.
Object Mothers
When we write our tests we often want to highlight the variables that are important to the test outcome i.e. this pre-condition state leads to this post-condition state. Other parts of setup however can obscure our test, so we tend to want to put into a creational method or setup. For example if it is important that my customer is in Washington I might write something like:
Customer customer = CreateTestCustomer();
customer.State = new State(“WA”);
When you push down this route you can find that you need a CreateTestCustomer on a number of test fixtures. This is especially true if you prefer a state based approach to testing. The idea behind Object Mother is that instead of these local creation methods we create a factory that we can dispense test objects from. This allows us to re-use the creation code, and prevents duplication, which feels like a good thing.
Customer customer = CustomerObjectMother.CreateCustomer();
customer.State = new State(“WA”);
Why did we stop using object mother?
We hit a slew of issues with object mother. The problem is what happens after the first person writes a mother for an object. Subsequent consumers do not always want to use the object with exactly that state. Either the data in insufficent or not what is needed for their test. The solution to this should be to either add another creational method:
Customer customer = CustomerObjectMother.CreateWashingtonBasedCustomer();
or to modify the object returned by the mother to the correct state.
Customer customer = CustomerObjectMother.CreateCustomer();
customer.State = new State(“WA”);
The latter is the better approach, because you usually need a variation because your test depends upon the returned object having a specific state. That is information that you should really encode into the test itself for clarity. If the reader cannot pick out the variables that affect the outcome of your tests from reading the test method itself, and have to go to other methods to understand that test, it increases the friction the reader experiences in understanding the sysem under test (SUT). In addition if we fall into the trap of depending on the state of the object returned by the mother, we end up with a shared fixture which makes our tests fragile. If someone changes the state of the object to something required for their test, then other tests may break. This is the classic fragile test problem that using a shared fixture leads us too.
So we wanted something that would encourage correct usage: modify the default object to explicity show the values that affect the outcome of the test.
TestDataBuilders
The basic idea behind a data builder is that we can quickly dispense constructed objects for use with tests by writing code like the following:
var product = new ProductBuilder().Build();
This should give you a object suitable for use in testing. Once again, you do not want to depend on the values in the properties of the object that we build. If your test does not care what the state of the object is, then you can just use the object returned by the vanilla build. If you do need the returned object to have a specific state you have two options. The first is to set the value explicitly. So if you wanted nto set the product code explicitly on your product you could write something like the following:
var product = new ProductBuilder().Build();
product.ProductCode = new ProductCode(“CAG”);
So far no there is no real difference. We could always do this from Object Mother. What we wanted was encouragement to set these values explicitly. With TestDataBuilder we support this by providing overrides for the properties that the builder sets. Conventions is to name these WithXXXXX, where XXXX is the property you want to set the value of, when the builder creates its object. We make these WithXXXX methods return the builder so that they
can be chained.
We want our test code to look something like:
var product = new ProductBuilder()
.WithProductCode(new ProductCode(“CAG”))
.Build();
The code for this might like something like this:
public class ProductBuilder
{
private ProductCode productCode = new ProductCode(“DAB”) ;
private String productName = “My Widget”
public Product Build()
{
return new Product
{
Code = productCode,
Name = productName
} ;
}
public ProductBuilder WithProductCode(ProductCode newCode)
{
productCode = newCode;
return this;
}
public ProductBuilder WithProductName(string newProductName)
{
productName = newProductName;
return this;
}
}
I would suggest providing a WithXXXXX for every property that you set by default in the builder. This makes it clear that you can override the default construction. The fluent interface syntax makes our intent very readable – the WithXXXX values are important to the test and will influence the outcome. There is no outcome difference between creating an object from a mother, and modifying its values, and using a builder and modifying its values. However, IMO, the builder syntax is easier to comprehend and thus more maintainable.
Either way the result is that a) we remove the dependency of our test on the values set in the builder – if we care about what they are we set them explicitly b) we highlight the values that are important to our test – by virtue of the fact that we set them we are saying that they effect what we is under test. Of course the builder pattern makes this easier, but, as usual, is not a silver bullet. You still have to remember that depending on the state of the vanilla build is fragile.
In addition TestDataBuilder helps when the you want to build Value Objects instead of Entities. Because Value Objects are usually immutable you cannot modify them once they are constructed. Because we cannot intercept the creation with Object Mother the only way to modify the dispensed object is to re-create the whole Value Object within your test. Because TestDataBuilder lets us set the properties that will be used at object creation, we can just supply the desired state to the builder so that it is created as required.
Where we have a complex dependency, one that might for example benefit from its own builder, I tend to include them as a WithXXXXXX so that you only have to add them if you need to use them to build your object. I have seen some people use a builder silently within another builder for their vanilla object. I am cautious on this as it could lead to nasty circular dependencies between your builders. The point is to have a simple vanilla build so that we do not have to buy complexity unless we need it.
If you make a second call to a builder you will get an exact copy of the object. This may be what you want, but it might not be as you might expect some values to be reinitialized, for example random names or id values. In that case create a new builder each time rather than re-using them.