Nowadays, there is no doubt that the quality of software and its flexibility are directly linked to the ability to verify if it still works after a change, a refactor or a new functionality. Time and effort are dedicated developing automated tests for our applications in order to keep adapting to changes while making sure our software keeps doing what it was doing before.
A technology that can help us considerably when verifying that our software works as expected is Test containers. Test containers is an open-source framework that allows us to deploy infrastructure dependencies of our application when running tests, so that we can test the application against real infrastructure dependencies.
We can deploy relational databases, non-relational databases, messaging brokers as well as any software deployable within a Docker container. We just need our tests to be able to communicate with a Docker daemon and deploy the test containers that we have configured in our test code.
¿What problems do Test containers solve?
By allowing us to automatically deploy infrastructure dependencies of our application, test containers makes it easier for us to write integration tests that make sure that the components of our application work correctly on the specific infrastructure. Without test containers, we would have to manage the instance of this infrastructure dependency externally. Using a shared external instance introduces problems and limitations when initializing schemes, fixture data, concurrent test executions by some developers, manual infrastructure deploy and so on.
With test containers, our tests always start with a new instance dedicated only for that test execution and we do not have to worry about all these situations.
Test containers in Spring Boot
Let’s put our hands on Test containers! In this article the purpose is going to be to test the persistence layer of a microservice. The microservice is a simple locations REST API, already used in other website articles such as Java 21 service with virtual threads performance test. The source code is available in the Github repository Spring Boot Location API. In order to start the service it is necessary to have Docker installed and a Java 21 JDK or higher.
The idea of this exercise is to prove that the application is able to use PostgreSQL and MongoDB as its persistence engine. Let’s start with PostgreSQL.
Adding the PostgreSQL infrastructure tests
Adding all these dependencies to our build.gradle
file we are adding the module to use test containers in Spring Boot and also the test container module
to be able to support PostgreSQL test containers.
The next step is going to be configuring the docker image we need to use in our test container. In order to use it in our Spring Boot tests it is going
to be necessary to initialize it as a Spring bean.
With the @ServiceConnection
annotation, Spring is going to configure the connection details against the deployed docker container. Now that our
tests are able to start a PostgreSQL instance, it is already possible to write the infrastructure integration tests.
The design pattern Template method test pattern has been chosen in order to be able to reuse our tests for each of the supported persistence engines.
The abstract class PersistenceIntegrationTest
is going to be used as a test template some test classes implementations may extend.
The PostgresIntegrationTest
inherits the PersistenceIntegrationTest
test methods and enables the postgres
Spring profile. This profile is
going to be the one used in the application to activate the PostgreSQL persistence and it will also initialize the LocationRepository
implementation
responsible for persisting our domain objects into the PostgreSQL RDBMS.
Adding the MongoDB infrastructure tests
The first step is to add the required modules for the mongo test container.
And the required bean to start the mongo test container.
As the last step, another inheritor class of the PersistenceIntegrationTest
is created in order to execute the MongoDB infrastructure integration tests.
At this point a project framework has been created in order to use a CI environment that guarantees in each build that our application still works against both database engines and also the exact docker image version of the database that we are using in our production environments.
Test containers and Github Actions
Closing the article, I wanted to remark the use of Github Actions as a simple and available CI engine. In the example repository it can be checked that Github Actions builds the artifact with Gradle, executing the tests with test containers as a build requirement.