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.

Redeploy application to Weblogic Server through Ant script

The most time consuming task in a Web app development is to redeploy the application into the server for every change and test it.

In Weblogic Workshop IDE, we have an option to redeploy the app into Weblogic server by just right clicking it. But in Intellij Idea, I was not able to find such feature. We spent considerable amount of time to redeploy application using 'Weblogic Console' until we figured out the 'Ant' script to do it.

<project>
<property name="bea.home" value="C:/bea"/>
<property name="wl.home" value="${bea.home}/weblogic92"/>
<property name="wl.domain" value="domainName"/>
<property name="wl.server" value="serverName"/>
<!-- classpath to have weblogic.jar as the first jar -->
<path id="base.path">
<fileset file="${wl.home}/server/lib/weblogic.jar"/>
<fileset dir="${wl.home}/server/lib/">
<include name="**/*.jar"/>
</fileset>
<pathelement path="${java.class.path}/"/>
</path>

<!-- weblogic tasks-->
<taskdef name="wldeploy" classname="weblogic.ant.taskdefs.management.WLDeploy">
<classpath refid="base.path"/>
</taskdef>
<taskdef name="wlserver" classname="weblogic.ant.taskdefs.management.WLServer">
<classpath refid="base.path"/>
</taskdef>

<target name="deployApp">
<wldeploy action="deploy" verbose="true" debug="true"
name="sampleApp" source="z:/app/target/sampleApp.ear"
user="weblogic" password="weblogic"
adminurl="t3://localhost:7001" targets="${wl.server}"/>
</target>

<target name="redeployApp">
<wldeploy action="undeploy" verbose="true" debug="true" name="sampleApp" 
user="weblogic" password="weblogic"
adminurl="t3://localhost:7001" targets="${wl.server}"/>
<wldeploy action="deploy" verbose="true" debug="true"
name="sampleApp" source="z:/app/target/sampleApp.ear"
user="weblogic" password="weblogic"
adminurl="t3://localhost:7001" targets="${wl.server}"/>
</target>
</project>

TIP:
Use SUBST command in Windows to map your project location to a new drive. For eg.,
subst z: d:/venky/projects/utils/sampleApp 

Individual developers in the team can checkout the code to any location but using this SUBST command will help the team to refer the project from same location like z:/src. Even though IDE's use relative path, it is nice to do this as the team can use these kinds of file without any customization.