Reuse Containers With Testcontainers for Fast Integration Tests

Last Updated:  August 27, 2021 | Published: June 21, 2020

Testcontainers is now for almost a year part of my core testing libraries set. It allows you to control Docker containers for external parts of your application (e.g. database or messaging queue). This helps a lot when you write integration tests. The integration of Testcontainers with Spring Boot and JUnit is excellent and they improve it even more with every release. When you use Testcontainers for your integration test, you'll recognize some delays when you launch the test. With the 1.12.3 release, they introduced a new feature (in alpha stage) to reuse containers. This improves the time spent for your integration tests and accelerates your build time.

Update: You can now find a visual demo of this Testcontainers feature on YoutTube.

Spring Boot with Testcontainers project setup

To demonstrate reusable containers with Testcontainers for a Spring Boot project, I'm using an application that requires a PostgreSQL database.

The application exposes one endpoint which retrieves all todos from the database. Testing is done with JUnit 5 (Jupiter) that is part of the Spring Boot Starter Test dependency (aka. swiss-army knife for testing Spring applications):

Furthermore, I'm using the Maven Failsafe plugin to run the integration tests separated from the unit tests:

By following the default convention, your integration tests need the postfix IT and the Failsafe plugin will pick them up automatically.

Requirements to reuse containers with Testcontainers

To enable the reuse of containers with Testcontainers, your setup needs the following:

  • use .withReuse(true) on your container definition
  • opt-in for reusable containers inside your ~/.testcontainers.properties file
  • use a manual container lifecycle control: singleton containers or respectively start the containers manually with e.g. @BeforeAll (not with a JUnit 4 rule or JUnit 5 extension)

The first parts two requirements are more or less trivial:

But the last requirement is where people often wonder why they can't reuse the container as they use either the JUnit 4 rule or the JUnit 5 extension. But both the rule and the extension (enabled with @Testcontainers)  tear down all containers after the test execution.

Reusing containers for integration tests

Let's start using this feature by writing the first integration tests.

The tests are a basic example of testing the Spring Boot application as a whole. I'm using Testcontainers to start the mandatory PostgreSQL database and verify that the endpoint is returning data.

As the setup for the different tests is similar, I'm using an abstract class to define the basic configuration for our integration tests:

With .withReuse(true) we're telling Testcontainers to reuse the container and not shut it down after the last test. Furthermore, I'm using a singleton container and inject the new data source URL, password and username using @DynamicPropertySource. This is available since Spring Boot 2.2.6. If you are using a version before 2.2.6 and/or use JUnit 4, take a look at this blog post where I explain the Testcontainers setup for different versions.

A first integration test can now extend the BaseIT and verify different parts of the application:

A second test can also reuse the common test configuration:

While these two tests would also share the same container (without the reuse feature) as the base class only starts the container when it's loaded, we still have a big benefit now. After the test execution, Testcontainers won't stop the container and still keep it running. This allows subsequent tests to use the already running container and save initial time for container setup.

Another important thing to note is that your container configuration has to match a previous configuration, as you won't be able to reuse an existing container otherwise.

Let's say you use PostgreSQL 9.6.12 but define two different database names and one integration test requires PostgreSQL 10 (for whatever reasons):

Testcontainers will then start the different containers as they can't be reused (which makes total sense). The result after executing the test is the following:

They are all up- and running after your test. If you now execute all your tests again, they will be significantly faster as Testcontainers can reuse them.

On my machine, these three integration tests take 20 seconds running them cold and 10 seconds while reusing containers.

Summary of reusing containers

Even though this feature is still in alpha state, it works great for basic usage of Testcontainers. Especially for local development and if you practice TDD (Test Driven Development), this sparks joy as your feedback cycle is now really short.

If your setup allows you to reuse existing containers with Testcontainers (e.g. database is cleaned after each test), this feature can save you a lot of time.  Also, you should ensure that the running containers can handle multiple connections as Spring Test provides a context caching mechanism. This allows you to spawn multiple application contexts during your test execution, all connecting to the same container (if you configure it in such a way).

The only thing you have to remember is that the containers are still running after your test execution. If you work on several projects throughout the day you might want to shut them down (e.g. docker rm $(docker ps -a -q)) to avoid conflicts or save some resources on your machine.

You can find the sample application on GitHub with instructions on how to run it on your machine.

Searching for more content on Testcontainers and writing efficient integration tests?

Have fun reusing your containers with Testcontaniners to accelerate your build times,

Phil

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

    Join Our Mailing List To Get 3x Free Cheat Sheets

    Free Java Cheat Sheets
    >