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.
A 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.