|

Singleton container with Testcontainers library

Testcontainers library is a perfect tool for providing Docker containers with external components from an integration test class level. With this tool, we are able to check how our codebase behaves with a real-life external system. But running an instance of a container for every test class sounds like poison for our patience, so it would be nice to create only one singleton container which is shared between all integration test classes in a one test execution context.

Shared container restrictions

When we would like to run a docker container for each test method, every start and stop of the container will take a massive amount of time. We will gain a clear environment for each test execution but lose our dignity. So it is better to make a compromise and keep some discipline in data management.

Let’s consider the case where we have a data layer in a container shared between whole integration tests execution. It means that all tests can read or write data from the same database and affects each other. To avoid data conflicts, our tests should use unique identifiers, values or whole objects to minimalise collisions. We should also be careful when we count items in the data layer because we can include in counting objects created not by a single test.

Of course, we can try to clean up the state of the data layer after each test execution but in that way, we have to add additional effort to the implementation and maintenance of the codebase.

Singleton container configuration

To define a container that is started only once and available for several test classes I created the abstract class which you can find below in the listening. There is the static field initialised by GenericContainer with the Redis service. What is important, this container is started inside the static block.

This solution provides a single container which can be used by all inheriting subclasses and by using static reference, we guarantee component start when the class is loaded.

public abstract class AbstractIntegrationTest {

    static final GenericContainer<?> redis =
            new GenericContainer<>(DockerImageName.parse("redis:7.0.4"))
                    .withExposedPorts(6379);

    static {
        redis.start();
    }

}

But what with a teardown of the container? We wrote only redis.strat()! No need to worry about it. The Testcontainers library uses the side container named Ryuk. It is responsible for removing containers, networks or volumes created by us during our integration tests. So it will stop and remove also our singleton container automatically at the end of the tests’ execution.

Singleton container usage

Now, to use our singleton container in test classes, we have to inherit from AbstractIntegrationTest class. I am going to show it with two simple test classes: FirstIntegrationTest and SecondIntegrationTest.

Both inherit from AbstractIntegrationTest class and try to test the logic of the RedisCache component. Because the redis field has the package-private modifier, we have the access to it in our test classes so we are able to easily use it for the initialisation of the System Under Test (sut) object.

public class FirstIntegrationTest extends AbstractIntegrationTest {

    private final RedisCache sut =
            new RedisCache(redis.getHost(), redis.getFirstMappedPort());

    @Test
    public void shouldPutValue() {
        // given
        String key = "Batman";
        String value = "Bruce Wayne";

        // when
        sut.put(key, value);

        // then
        assertEquals(Optional.of(value), sut.get(key));
    }

}

Both classes use unique objects in the test execution context so we are protected against problems related to data collisions.

public class SecondIntegrationTest extends AbstractIntegrationTest {

    private final RedisCache sut =
            new RedisCache(redis.getHost(), redis.getFirstMappedPort());

    @Test
    public void shouldReturnOptionalEmpty() {
        // given
        String key = "Joker";

        // when
        Optional<String> result = sut.get(key);

        // then
        assertEquals(Optional.empty(), result);
    }

}

Testcontainers library extension

The Testcontainers library provides the extension for the JUnit Jupiter library by adding some annotations which handle the lifecycle of containers. @Testcontainers and @Container can be used with static fields but then we will not be able to reuse singleton containers between different test classes. What is more, unfortunately, this extension works only for test execution in one thread. Parallel test execution is not supported and could cause some unexpected results.


How to configure and start using the Testcontainers library I described here.

Other useful links:

All examples from this post you can find on my GitHub here

When we write integration tests, it would be nice to check if our feature works fine with a real-life service. The Testcontainers library matches really great to this task and allows us to run docker containers from the java test codebase without any complicated configuration. I hope the singleton containers will help you to create your own independent integration tests.

The same programmers as us work everywhere.
Oskar K. Bogacz

Similar Posts