- Quickstart
- Free plan
- Professional plan
- Learning Bootify
- Setup Basic Project
- Working with Data Objects
- Adding custom REST APIs
- Best Practices
- Schema Generation
- Integration Tests
- Spring Security & JWT
- Working with IntelliJ
- Using Lombok
Adding Integration Tests in Spring Boot with Testcontainers
Good software always includes automated tests to detect errors during the creation and modification of the code. In this tutorial, integration tests will be added to a Spring Boot application, connecting to the 'real' database using the Testcontainers library.
Backgrounds
An integration test is meant to test the interaction of multiple parts of the application. So for a Spring Boot application, it would be great to examine the running app including the database (e.g. MySQL or PostgreSQL). An embedded database would not be optimal for this, as there can be significant differences to the actual database, and therefore certain features may not work or errors may be missed.
For this purpose, there is the Testcontainers library to load a Docker image of exactly the database that is used by the developer and later in production. To write an integration test for our application, we first add the dependencies.
Dependencies of build.gradle
Besides spring-boot-starter-test
we need org.testcontainers:junit-jupiter
as well as an org.testcontainers
reference to the database we have in use. In our case
we choose MySQL.
Starting the application context
To centralize the recurring logic of our test classes, we first create an abstract base class from which our IT classes then inherit. The first version of this class looks like this:
First version of BaseIT.java
Spring Boot provides very good support for integration testing by using the
@SpringBootTest
annotation to load the full application context.
With @ActiveProfiles
we define that the application is started with the profile
"it". If needed, we can use this profile to activate special settings in our code.
By setting the variable webEnvironment
to RANDOM_PORT
the application will be
started under a random (and available) port. This will be picked up automatically by the
TestRestTemplate, when using it to send calls to our application's REST API - more on that later.
Mocking the database
Now we want to configure a database that is exclusively available for our tests. For this we extend BaseIT with the following code snippet:
Extending our BaseIT class with the Testcontainers setup
In the static section, the MySQLContainer is configured and started. The string
"mysql:8.0"
defines the Docker image to be used. This should ideally
exactly match the version that is also used in production. With the parameter
withReuse(true)
, the Docker container is not automatically terminated at the end
of the tests, but available for reusage without a longer waiting period. Occasionally, the
container must be terminated manually to fully reset the database.
To enable reuse, the file /<usersdir>/.testcontainers.properties
must be extended
by the entry testcontainers.reuse.enable=true
.
In the section after @DynamicPropertySource
we set the properties to
link our application context to the Docker containers database.
Depending on how we initialize our database schema - for example with Flyway or
Liquibase - this will be automatically applied during context startup.
Running the tests
With this done, we can write our first test class. Let's
assume that the following @RestController
already exists. The referenced service
returns all entries that exist in the "Test" table.
TestController of our Spring Boot app
We can now create a class TestControllerIT
that extends our abstract base class.
The test method sends a GET request to the existing endpoint using our TestRestTemplate
.
TestControllerIT of our Spring Boot app
Even though our database schema is already initialized, we still lack explicit test data
that we need for our test. For this we use the Spring annotation @Sql
, which
executes two scripts and thus puts our database into a known state.
In our case, there is now exactly one entry in the Test table that we expect as a result.
Wiping out all data with src/test/resources/data/clearAll.sql
Create a single table entry with testData.sql
With restTemplate.exchange(...)
we send a GET request to /api/tests
.
The functionality is basically the same as we already know from Spring's
RestTemplate
class. Our defined request does not send any data (null
).
The response is deserialized to type List<TestDTO>
and in the assertions we check
that the HttpStatus
and the list match our expectation.
When we run the test for the first time, we have to wait a bit for the Docker
container to start, depending on our environment. It may also be useful to already cache the
image beforehand using docker pull mysql:8.0
to avoid issues with the download.
Our test should now go through without errors.
Conclusion
With the setup described, we have created a way to check the behavior of our application including the database from a high level. With this we have a very good addition to our unit tests. Testcontainers also offers support for other services such as RabbitMQ, so we can flexibly extend BaseIT as needed.
In the professional plan, Bootify offers the option to activate Testcontainers. This initializes the Spring Boot application including the described setup, depending on the selected database. It also generates the IT classes and scripts according to the tables and controllers created.
Further readings
Testcontainers Homepage
Official guide on testing Spring Boot applications
Docker Hub for finding images
@Sql and @SqlMergeMode explanation
Where to look for the .testcontainers.properties
See Pricing
or read quickstart