About Rafal Borowiec

Rafal is an IT specialist with about 8 years of commercial experience, specializing in software testing and quality assurance, software development, project management and team leadership.

Test Data Builders and Object Mother: another look

Constructing objects in tests is usually a painstaking work and usually it produces a lot of repeatable and hard to read code. There are two common solutions for working with complex test data: Object Mother and Test Data Builder. Both has advantages and disadvantages, but (smartly) combined can bring new quality to your tests.

Note: There are already many articles you can find about both Object Mother and Test Data Builder so I will keep my description really concise.
 
 
 

Object Mother

Shortly, an Object Mother is a set of factory methods that allow us creating similar objects in tests:

// Object Mother
public class TestUsers {

    public static User aRegularUser() {
        return new User("John Smith", "jsmith", "42xcc", "ROLE_USER");
    }

    // other factory methods

}

// arrange
User user = TestUsers.aRegularUser();
User adminUser = TestUsers.anAdmin();

Each time when a user with slightly different variation of data is required, new factory method is created, which makes that the Object Mother may grow in time. This is one of the disadvantages of Object Mother. This problem can be solved by introducing a Test Data Builder.

Test Data Builder

Test Data Builder uses the Builder pattern to create objects in Unit Tests. A short reminder of a Builder:

The builder pattern is an object creation software design pattern. […] The intention of the builder pattern is to find a solution to the telescoping constructor anti-pattern.

Let’s look at the example of a Test Data Builder:

public class UserBuilder {

    public static final String DEFAULT_NAME = "John Smith";
    public static final String DEFAULT_ROLE = "ROLE_USER";
    public static final String DEFAULT_PASSWORD = "42";

    private String username;
    private String password = DEFAULT_PASSWORD;
    private String role = DEFAULT_ROLE;
    private String name = DEFAULT_NAME;

    private UserBuilder() {
    }

    public static UserBuilder aUser() {
        return new UserBuilder();
    }

    public UserBuilder withName(String name) {
        this.name = name;
        return this;
    }

    public UserBuilder withUsername(String username) {
        this.username = username;
        return this;
    }

    public UserBuilder withPassword(String password) {
        this.password = password;
        return this;
    }

    public UserBuilder withNoPassword() {
        this.password = null;
        return this;
    }

    public UserBuilder inUserRole() {
        this.role = "ROLE_USER";
        return this;
    }

    public UserBuilder inAdminRole() {
        this.role = "ROLE_ADMIN";
        return this;
    }

    public UserBuilder inRole(String role) {
        this.role = role;
        return this;
    }

    public UserBuilder but() {
        return UserBuilder
                .aUser()
                .inRole(role)
                .withName(name)
                .withPassword(password)
                .withUsername(username);
    }

    public User build() {
        return new User(name, username, password, role);
    }
}

In our test we can use the builder as follows:

UserBuilder userBuilder = UserBuilder.aUser()
    .withName("John Smith")
    .withUsername("jsmith");

User user = userBuilder.build();
User admin = userBuilder.but()
    .withNoPassword().inAdminRole();

The above code seem pretty nice. We have a fluent API that improves the readability of the test code and for sure it eliminates the problem of having multiple factory methods for object variations that we need in tests while using Object Mother.

Please note that I added some default values of properties that may be not relevant for most of the tests. But since they are defined as public constants they can be used in assertions, if we want so.

Note: The example used in this article is relatively simple. It is used to visualize the solution.

Object Mother and Test Data Builder combined

Neither solution is perfect. But what if we combine them? Imagine, that Object Mother returns a Test Data Builder. Having this, you can then manipulate the builder state before calling a terminal operation. It is a kind of a template.

Look at the example below:

public final class TestUsers {

    public static UserBuilder aDefaultUser() {
        return UserBuilder.aUser()
                .inUserRole()
                .withName("John Smith")
                .withUsername("jsmith");
    }

    public static UserBuilder aUserWithNoPassword() {
        return UserBuilder.aUser()
                .inUserRole()
                .withName("John Smith")
                .withUsername("jsmith")
                .withNoPassword();
    }

    public static UserBuilder anAdmin() {
        return UserBuilder.aUser()
                .inAdminRole()
                .withName("Chris Choke")
                .withUsername("cchoke")
                .withPassword("66abc");
    }
}

Now, TestUsers provides factory method for creating similar test data in our tests. It returns a builder instance, so we are able to quickly and nicely modify the object in a our test as we need:

UserBuilder user = TestUsers.aUser();
User admin = user.but().withNoPassword().build();

The benefits are great. We have a template for creating similar objects and we have the power of a builder if we need to modify the state of the returned object before using it.

Enriching a Test Data Builder

While thinking about the above, I am not sure if keeping a separate Object Mother is really necessary. We could easily move the methods from Object Mother directly to Test Data Builder:

public class UserBuilder {

    public static final String DEFAULT_NAME = "John Smith";
    public static final String DEFAULT_ROLE = "ROLE_USER";
    public static final String DEFAULT_PASSWORD = "42";

    // field declarations omitted for readability

    private UserBuilder() {}

    public static UserBuilder aUser() {
        return new UserBuilder();
    }

    public static UserBuilder aDefaultUser() {
        return UserBuilder.aUser()
                .withUsername("jsmith");
    }

    public static UserBuilder aUserWithNoPassword() {
        return UserBuilder.aDefaultUser()
                .withNoPassword();
    }

    public static UserBuilder anAdmin() {
        return UserBuilder.aUser()
                .inAdminRole();
    }

    // remaining methods omitted for readability

}

Thanks to that we can maintain the creation of User’s data inside a single class.

Please note, that in this that Test Data Builder is a test code. In case we have a builder already in a production code, creating an Object Mother returning an instance of Builder sounds like a better solution.

What about mutable objects?

There are some possible drawbacks with Test Data Builder approach when it comes to mutable objects. And in many applications I mostly deal with mutable objects (aka beans or anemic data model) and probably many of you do as well.

The Builder pattern is meant for creating immutable value objects – in theory. Typically, if we deal with mutable objects Test Data Builder may seem like a duplication at first sight:

// Mutable class with setters and getters
class User {
    private String name;
    public String getName() { ... }
    public String setName(String name) { ... }

    // ...
}

public class UserBuilder {
    private User user = new User();

    public UserBuilder withName(String name) {
        user.setName(name);
        return this;
    }

    // other methods

    public User build() {
        return user;
    }
}

In a test we can then create a user like this:

User aUser = UserBuiler.aUser()
    .withName("John")
    .withPassword("42abc")
    .build();

Instead of:

User aUser = new User();
aUser.setName("John");
aUser.setPassword("42abc");

In such a case creating Test Data Builder is a trade off. It requires writing more code that needs to be maintained. On the other hand, the readability is greatly improved.

Summary

Managing test data in unit tests is not an easy job. If you don’t find a good solution, you end up with plenty of boilerplate code that is hard to read and understand, hard to maintain. On the other hand there is not silver bullet solution for that problem. I experimented with many approaches. Depending on the size of the problem I need to deal with I select a different approach, sometimes combining multiple approaches in one project.

How do you deal with constructing data in your tests?

Resources

Reference: Test Data Builders and Object Mother: another look from our JCG partner Rafal Borowiec at the Codeleak.pl blog.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

Leave a Reply


six × = 48



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close