The builder pattern for test classes

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.