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.