Trifork Blog

Testing with factories

July 10th, 2009 by
| Reply

On July 1st, Jelmer added a very useful blog post about testing the database layer in which he suggested to use "insert statement" and "fixture" classes to provide a good way to insert test data into your database. I am also using that technique as I'm writing unit tests for the database layer. I have to say this really makes unit tests less work to write, more focussed and therefore more fun to write!

Although this technique solves the challenges we have in database layer tests, we might encounter similar challenges when writing unit tests for other layers (such as the service or frontend layer). This is because such tests often also require "test data" you need to create. Only this time the test data is not in the form of database rows, but in the form of Java objects. Such objects need to be instantiated in a valid state and often depend on other objects. I find this quite similar to inserting rows into database tables having not null columns and/or foreign key constraints.

The challenge

As an example I made up the following unit test (using JUnit):

@Test
public void testCalculateTotalOrderPrice() {
    Address address = new Address("Some Street", "13a", "1234AB", "City");
    User owner = new User("Firstname", "Lastname", address);
    Order order1 = new Order(Status.SENT, owner, new BigDecimal("15"));
    Order order2 = new Order(Status.SENT, owner, new BigDecimal("35"));

    BigDecimal totalPrice = service.calculateTotalOrderPrice(Arrays.asList(order1, order2));

    assertEquals(new BigDecimal("50"), totalPrice);
}

Here we test that the total price of two orders is correctly calculated. The only thing we care about in this unit test is the price of both orders. But as you can see, to create an Order in a valid state, we need to set the Status and the user who owns the order as well. But to create the owning User we have to create it in a valid state as well. And in turn a User needs a valid Address instance. Such dependencies are quite common in for example your domain model (Hibernate/JPA entities).

We will also soon find ourselves copying and pasting the four lines of Order instantiation code in other tests that also require one or more valid Order instances. So not only it requires a lot of code to create an Order, we also copy and paste the same code all over the place.

Factories

Inspired by the "insert statement" technique we want a similar class that can help our tests create Order instances and provide default values for properties we don't care about. Let's call such a helper class a "factory". This should reduce test code and allows us to only care about the price and not about any other fields of the order, or the fields of any of the order's dependencies. This is what the factory looks like:

public class OrderFactory {

    private Status status;
    private User owner;
    private BigDecimal price;

    public OrderFactory() {
        // Initialize the factory with default values
        status = Status.SENT;
        owner = new UserFactory().newInstance();
        price = new BigDecimal("9.95");
    }

    public Order newInstance() {
        return new Order(status, owner, price);
    }

    public OrderFactory setPrice(BigDecimal price) {
        this.price = price;
        return this;
    }
}

A factory should always have a no-arguments constructor which initializes defaults used for objects created by this factory. This way you can instantiate a factory without supplying any values at all. If a test does want to supply values, setter methods can be used.

Each factory is responsible for creating instances of a single class. This way you can easily instantiate this factory using only default values for the fields. Notice that the above factory delegates instantiating a new User to another factory. That UserFactory follows the exact same principle and should possibly delegate the creation of an Address to an AddressFactory. Each of these factories could be reused in other tests.

Having the OrderFactory in place helps us write the same test in a more efficient way:

@Test
public void testCalculateTotalOrderPrice() {
    Order order1 = new OrderFactory().setPrice(new BigDecimal("15")).newInstance();
    Order order2 = new OrderFactory().setPrice(new BigDecimal("35")).newInstance();

    BigDecimal totalPrice = service.calculateTotalOrderPrice(Arrays.asList(order1, order2));

    assertEquals(new BigDecimal("50"), totalPrice);
}

Now we don't have to care about anothing other than the price of an Order. All other required properties an Order has, we don't have to set in the unit test. Also, dependencies of an Order are taken care of by the OrderFactory. Notice that we used a fluent interface for the factory so we can write the code for creating an Order in one statement. The fluent interface also makes the code more readable. Feel free to add any number of setter methods to any factory, as long as you only call them when your test cares about the value being set. Each of the three factory classes needed for this test (OrderFactory, UserFactory and AddressFactory) can also be reused in other tests. For example in tests that work with Users or with Addresses. As with InsertStatements never rely on defaults! (see Jelmer's blog) Any value your test relies on should be made explicit, even if it is the same value as the default.

So as you can see, these "factories" use similar principles as the "insert statements", but they apply to objects instead of database table rows. I have used factories for a while now and it makes writing unit tests take less time and more fun. Because once you have a lot of factories in place, new unit tests can easily (re)use them in other situations.

3 Responses

  1. July 14, 2009 at 11:29 by Andrea Del Bene

    Hi Tom,

    I've adopted a similar approach in a Spring based project. I've declared my test data entities in a Spring xml context file and loaded it in test case classes with others context file.

    you can find the test entites file at thi url:

    https://jugancona.dev.java.net/source/browse/jugancona/Jug4Tenda/src/org/jugancona/jug4tenda/businessServices/test/LoadDataTest.xml

  2. September 16, 2009 at 11:58 by Peter Schuler

    Uri Boness pointed me to this post and I think this is a very good idear and I will implement it myself.

    As a point of discussion: I will put the factories into the domain/src/test/java and share them across the different unittest layers. But is this the way to go?

    And can you chain your factories in order to create complex object trees needed to test high level services? Or will this make your testcode too complex?

  3. September 17, 2009 at 09:28 by Peter Schuler

    If you want your DomeinFactory to be avaible in higher level modules in maven check out this blog:
    http://www.waltercedric.com/java-j2ee-mainmenu-53/361-maven-build-system/1349-maven-reusing-test-classes-across-multi-modules-projects.html

Leave a Reply