I’ve been working in my current project for a while now and while it has its problems I mostly enjoy my envolvement in it. The project is based on Spring Boot and consists of a bunch of web-applications, both for endusers, internal personnel and operations staff. The team takes testing seriously, we have different code-quality tools integrated into the build – so all-in-all it’s on the right track.
Still, there are some problems and recently I got annoyed with one: Most of the integration tests work against the same local database that developers are using when they run the applications locally. This sometimes leads to test failures because the database can contain entries that those tests don’t expect. Also, there’s a risk that tests don’t run in a transaction and forget to clean up the database, after they have run.
We have migrated some of these tests to use an in-memory database, but most of them rely on a setup that uses database specific commands that cannot be executed against an in-memory database. To be more specific: We’re using Postgres as a production database and while H2 (the default in-memory database provided by Spring Boot) understands some Postgres-specific commands, there are many that behave a bit different or can’t be run at all. Since we are using Flyway for database migrations it’s difficult to change existing sql migrations to be executable by both, Postgres and H2.
Docker Compose Rule
Some time ago my colleague Thomas wrote an article in which he explained the usage of the Docker Compose Rule. Since there are many different docker containers to choose from, this sounded like a perfect fit for our problems. And indeed, it didn’t take long to migrate one of our tests to run against a docker-based Postgres container. Michael Simons wrote a similar post a few days ago, his approach increases on the integration with Spring Boot. Here’s an example using Michael’s approach in a vanilla Spring Boot project.
The Docker Compose Rule is implemented as a JUnit 4 Rule and there’s no official support for JUnit 5, yet. I understand that this is a topic for one of the next releases. There’s an experimental JUnit 5 extension, however, you can find it here.
Having played with the Docker Compose Rule I then learned about the Testcontainers project (thanks Tim!) and decided to have a look at it as well. It’s a library that encapsulates the specifics of Docker and provides different containers that you can use for integration testing. One of them is focused on Postgres, but if you are looking for one that isn’t available you can always use a generic container with the docker image you need. If your dependencies are more complex there’s the DockerComposeContainer: it allows you to specify a docker-compose.yml file in which you can define your dependencies.
Testcontainers is also implemented as a JUnit 4 Rule, you can use it as a Class-Rule to spin up a container for all methods of a test class, or you can use it as a Method-Rule, if you want new containers for every single test method.
Again, there’s no JUnit 5 extension yet, but it’s quite easy to start and stop a container manually using the available lifecycle annotations @BeforeAll, @BeforeEach, @AfterAll and @AfterEach.
Since there’s a comprehensive documentation on the project webpage it’s easy to get started. Here’s a sample repository that shows a simple usage of Testcontainers. There are more examples on the project page.
Both of these approaches work fine to address our problem with integration tests. I like Testcontainers a bit better because it’s easier to get started if you can use one of the provided default containers and offers a lot of flexibility through generic containers and docker compose containers. Also, I couldn’t make the Docker Compose Rule run as a method rule, which makes it slightly less versatile.
In order for any of those tools to work you will have to install Docker not only on your developer machines but also on your build servers. For the Docker Compose Rule you will also have to have Docker Compose installed, while Testcontainers starts Docker Compose in its own container.