May 05, 2012

An Affair Between ObjectMother And TestDataBuilder

I've read a handful of blog posts discussing whether to choose ObjectMother or TestDataBuilder for test data creation. Most of them goes like this: "I've been using ObjectMother, but now I have found the TestDataBuilder and I'm ditching the ObjectMother for it". Others warn you that "ObjectMother is just another name for GodClass antipattern and you should never use it - use TestDataBuilder". Reading those one could easily think that ObjectMothers are not good for anything.

But wait a moment! If your ObjectMother gets bloated and filled with methods like createAFilthyRichCustomerWhoDrivesAPorsche() then it is not the ObjectMother to blame, but you and your fellow programmers. Any class can become a GodClass if one decides to make it such. You can always build a horrendous test data setup and hide it somewhere, too.

ObjectMother should not be a dump for miscellaneous methods that give name to a multipage test data creation vomit, but a helper that offers centralized methods to create test data. Instead of createAFilthyRichCustomerWhoDrivesAPorsche() it could have methods like Customer newCustomer(String name) and addCars(Customer customer, Car... cars). Often you will find that the methods in ObjectMother are useful for the production code and you end up moving some of them there.

I think it is a good approach to start with ObjectMother and introduce the TestDataBuilder when there is a need to create more complex objects or to avoid dependency injections. At start the ObjectMother could create the customer like this:

public Customer newCustomer(String name){
    Customer customer = new Customer(name);
    customer.setFunds(1000);  
    return customer;



Later on  when customer gets more complicated you can easily let the ObjectMother use TestDataBuilder:

public Customer newCustomer(String name){
    Customer customer = testCustomer().name(name).build();
    return customer;




public CustomerBuilder testCustomer(){
return new CustomerBuilder().defaultData();
}

Now ObjectMother works as a dictionary for existing TestDataBuilders and it has methods that ease up their use. So it really works as a helper for building test data.

My TestDataBuilders don't add default data to the created test objects unless it is asked from them. We started with builders that did, but soon found out we wanted to use the empty versions too. We have data with deep hierarchies and for some test cases we just wanted an empty data with all child elements at place and empty too. So I added a separate method for adding a default data and a method verifyDefaultData() to verify that data.

You also might have noticed how I said you can use TestDataBuilder to avoid dependency injections. I don't advocate dependency injections. Most often they are used to inject critical dependencies and adding public or protected methods to access them just breaks encapsulation. I rather give dependencies in constructor and raise an exception if they are missing. TestDataBuilder lets you construct the test data in phases just like dependency injection would.

@Test
public void testCustomerIsAddedToDb(){

    Db dbMock = mock(Db.class);
    Register register =  mother.testRegister().db(dbMock).build();
    Customer john = mother.newCustomer("John");
    register.addCustomer(john);
    verifyCustomerAddedToDb(john, dbMock);
}

What I also don't advocate is wrapping TestDataBuilders inside each other. You might be tempted to do that to make code look cleaner, but please don't. There are several reasons against. Every now and then you'll want to feed the TestsDataBuilder a real item instead of a builder. You'll end up having to implement both customer(CustomerBuilder builder) and customer(Customer customer). Also, when you give your builder away you no longer see when the build is actually called. Look at this:

public void createAProblem(){
    CustomerBuilder builder = mother.testCustomer();
    AddressBuilder aBuilder = mother.testAddress();
    builder.address(aBuilder);
    aBuilder.street("Abbey Road");
    Customer customer = builder.build();
}

Can you tell me what street the customer lives in? You'll have plenty of work dealing with real problems so keep your builders clean and simple.

If you want to read more about managing test data then Jay Fields' Creating Objects in Java Unit Tests is a nice read. Jay is using something he calls DomainObjectBuilder in place of the ObjectMother. I think the idea is the same though - single access point to TestDataBuilders.