November 15, 2010

Builders to setup Unit test data

Exposing Setter methods or constructors in domain objects for setting up Unit test data should never be done.
Builder pattern is a simple alternate to setup unit test data using reflection without exposing unnecessary setters and constructors in domain objects.

1) Abstract builder:
Create an Abstract builder that will be extended by all domain builder objects.
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AbstractBuilder<E> {
    private E domain;

    public AbstractBuilder(E domain) {
        this.domain = domain;
    }

    public E build() {
        try {
            List<Field> domainFields = getAllFields(new ArrayList<Field>(), domain.getClass());
            Map<String, Field> builderFieldsMap = index(getAllFields(new ArrayList<Field>(), this.getClass()));
            for (Field domainField : domainFields) {
                Field builderField = builderFieldsMap.get(domainField.getName());
                if (builderField != null) {
                    copyFieldValue(domainField, builderField);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return domain;
    }

    private void copyFieldValue(Field domainField, Field builderField) throws IllegalAccessException {
        domainField.setAccessible(true);
        builderField.setAccessible(true);
        domainField.set(domain, builderField.get(this));
    }

    private Map<String, Field> index(List<Field> allFields) {
        Map<String, Field> map = new HashMap<String, Field>();
        for (Field allField : allFields) {
            map.put(allField.getName(), allField);
        }
        return map;
    }

    public static List<Field> getAllFields(List<Field> fields, Class<?> type) {
        for (Field field : type.getDeclaredFields()) {
            fields.add(field);
        }
        if (type.getSuperclass() != null) {
            fields = getAllFields(fields, type.getSuperclass());
        }
        return fields;
    }

    public E getDomain() {
        return domain;
    }
}

2) Domain builder class:
Consider a domain class Person with 4 attributes (firstName, lastName, gender, dateOfBirth). You should create the domain builder object with same 4 attributes.

public class PersonBuilder extends AbstractBuilder<Person> {
    private String firstName;
    private String lastName;
    private String gender;
    private Date dateOfBirth;

    public PersonBuilder() {
        super(new Person());
    }

    public PersonBuilder withFirstName(String firstName) {
        this.firstName = firstName;
        return this;
    }

    public PersonBuilder withLastName(String lastName) {
        this.lastName = lastName;
        return this;
    }

    public PersonBuilder withGender(String gender) {
        this.gender = gender;
        return this;
    }

    public PersonBuilder withDateOfBirth(Date dateOfBirth) {
        this.dateOfBirth = dateOfBirth;
        return this;
    }
}

3) Using Builders in Unit Tests:

You can use the builder as shown below
Person testPerson = new PersonBuilder().withFirstName("hello").withLastName("World").build();

Tip:
If you are using Intellij IDEA as the IDE, use Builder plugin to generate the "with" methods quickly.

2 comments:

  1. You rock ! Wow. This helped big time in writing my domain tests.
    -Sriram Narasimhan

    ReplyDelete
  2. Thanks for posting....makes creating builders so much less work.

    ReplyDelete