JUnit5 TestSuite Alternative

JUnit4 had the TestSuiteclass to aggregate multiple tests. This is not available in JUnit 5. Generally test discovery via a bunch of named tests in a suite somewhat sucks. However, if the aim is not test-discovery, but the sharing of resources between different test classes, then it makes sense to want to create a parent.

JUnit 5 provides the @Nested annotation to allow a child class to run inside the context of its parent. The assumption is that the child class is non-static, so has access to the instance values of its parent. If we want to share test resources, we probably want to think about the class-level setup of the test suite, and somehow wiring that into the class-level setup of our child classes.

Let’s contrive a fake example that demonstrates the problem:

@Testcontainers // use docker images
class MyTest {
    // make a DB at the start of the test in a docker container
    // takes a few minutes to boot up
    @Container
    private static final DatabaseContainer DB = createDbContainer();

    private static MyDao dao;

    @BeforeAll
    static void beforeAll() {
        dao = createDaoFrom(DB);
    }

    @Test
    void daoFeatureOne() {
       assertThat(dao.find("no data")).isEmpty();
    }
}

The above is a test which starts up a database in the global lifecycle of the test class. It wires up a dao object to it, and can have multiple tests that reuse that dao.

In an ideal world we could reset everything for each test, but a database is an expensive resource to start up. Maybe we can add some beforeEach and afterEach hooks to clean its data, but we wouldn’t want to bounce the database. Each time. Similarly, some framework startup cost for our dao may be undesirable if run every time.

The above, as the one and only test in our project would be fine, but what if there are other tests that also need this database… and what if it really takes AGES to run…

There’s No Suite in JUnit 5

Annoying isn’t it. If only we could do:

@JUnit5TestSuite // not real
@Children({MyDaoTest.class, MyOtherDaoTest.class})
@Testcontainers
class MyTestSuite {
    @Container
    private static final DatabaseContainer DB = createDbContainer();

}

That would be brilliant… but it would leave us with some questions:

  • How do we ensure the child tests don’t run outside of the suite?
  • How do these tests access the `DB` object?

A Sort of Suite Alternative

Let’s imagine that we have a static method getDb to provide the database when we need it.

Now let’s rewrite the original DaoTest to use it, and make it abstract so the test runner won’t pick it up:

abstract class MyTestImpl implements DbProvider {
    private static MyDao dao;

    @BeforeAll
    static void beforeAll() {
        // access to the database container
        // from the static method (statically imported)
        dao = createDaoFrom(getDb());
    }

    @Test
    void daoFeatureOne() {
       assertThat(dao.find("no data")).isEmpty();
    }
}

Now we’ve got a partial test that could be run in a suite, let’s define the suite. Let’s also use @Nested to wire in the child class:

@Testcontainers // use docker images
class MyTest {
    // make a DB at the start of the test in a docker container
    // takes a few minutes to boot up
    @Container
    private static final DatabaseContainer DB = createDbContainer();

    // provide the getDb function to access the container
    public static DatabaseContainer getDb() {
        return DB;
    }

    // test suite members are just nested classes that extend
    // the abstract class of each member of the suite
    @Nested
    class MyTest extends MyTestImpl {
    }

    // ... add more suite members with more @Nested
}

Disadvantages

With classes operating on each other’s static bits, this runs the risk of getting confusing.

The fact that each nested class needs to be a subclass is a bit funky too…

But this works and makes for an effective test suite.

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s