The builder pattern for test classes

Using the builder pattern to build and persist test object at once

I’ve always been interested in software testing, either unit tests, integration tests, ui tests, … Not because it’s the funniest part of software development but because I think it’s important to deliver quality and testing is the main way to achieve it.

Recently, I wrote a lot of integration tests for an API with quite complex object trees and to make effective tests, I need to insert effective data.

Our APIs are generated using Jhipster, the following code snippet shows how a Jhipster application creates and persists objects in integration tests.

createEntity method is generated, being responsible for instantiating a class, eventually calling a createEntity from another test class to create an instance of a dependency.

public class NodeResourceIntTest {

	@Autowired
	private EntityManager em;

	private Node node;

	public static Node createEntity(EntityManager em) {
		node = new Node()
			.lft(DEFAULT_LFT)
			.rgt(DEFAULT_RGT)
			.nodeOnline(DEFAULT_NODE_ONLINE)
			.lastChangeAt(DEFAULT_LAST_CHANGE_AT);
		// Add required entity
		Tree tree = TreeResourceIntTest.createEntity(em);
		em.persist(tree);
		em.flush();
		node.setTree(tree);
		return node;
	}

	@Before
	public void initTest() {
		node = createEntity(em);
	}
}

This example remains simple. What if I tell you that a Tree object is also linked to several children of type Nodes, themselves having a set of MetaGroup, each MetaGroup having a MetaType and a set of Meta, … and so on.

Some tests don’t need the full hierarchy but for some others, a full hierarchy is required for the test to be relevant.

A convenient way to create objects is the builder pattern. Creating a Node object could look like this.

Node node = new NodeBuilder()
    .withLft(DEFAULT_LFT)
    .withRgt(DEFAULT_RGT)
    .withNodeOnline(DEFAULT_NODE_ONLINE)
    .withLastChangeAt(DEFAULT_LAST_CHANGE_AT)
    .build();

Good enough for a unit test, but I also do a lot of integration tests and therefore, I want my objects to be added to my persistence context and stored in my database. Therefore, I added a buildAndPersist method to my builder and to be able to persist it, I needed an EntityManager, which I pass to the builder via the constructor.

Node node = new NodeBuilder(entityManager)
    .withLft(DEFAULT_LFT)
    .withRgt(DEFAULT_RGT)
    .withNodeOnline(DEFAULT_NODE_ONLINE)
    .withLastChangeAt(DEFAULT_LAST_CHANGE_AT)
    .buildAndPersist();

I ended up building an abstract class, so that all my builders would follow the same behavior.

public abstract class AbstractPersistenceTestBuilder<T> {

    private final EntityManager em;

    public AbstractPersistenceTestBuilder(EntityManager em) {
        this.em = em;
    }

    public T buildAndPersist() {
        T target = build(); // Builds the object via the implementation provided by the concrete class
        em.persist(target); // Persists the created object
        return target; // Returns the instance linked to the persistence context
    }

    public abstract T build();
}

From now on, building a complex object can be as simple as this code snippet, which in my opinion, also make the code much easier to read.

Tree tree = new TreeTestBuilder(em)
      .withName("firstTree")
      .withCategory("category")
      .withCode(UUID.randomUUID().toString())
      .withLastChangeAt(now())
      .withMetaGroups(newHashSet(new MetaGroupTestBuilder(em)
            .withMetaType(new MetaTypeTestBuilder(em)
                  .withName("title")
                  .withProtectedType(TRUE)
                  .withValueType(MetaTypeValueType.TEXT)
                  .withLastChangeAt(now())
                  .buildAndPersist())
            .withMetas(newHashSet(new MetaTestBuilder(em)
                  .withMetaValue("my lovely title")
                  .withContextLanguage(ContextLanguage.EN)
                  .withLastChangeAt(now())
                  .buildAndPersist()))
            .buildAndPersist()))
      .withLabels(newHashSet(labelLevel))
      .buildAndPersist();

The source code is available on my Github.

In a next post, I hope to publish this very simple library to Maven Central as an exercise.