Recently, I was looking for a solution to write integration tests for my Spring Boot-based application which was using PostgreSQL. I had the following requirements for this task:
- The integration tests should use the same database as in production (referring to the Twelve-Factor App I wanted to keep my environment during the tests as similar as possible to the production environment)
- The tests should not need any pre-setup before running (e.g. like manually setting up a test database)
- The tests should use my Flyway DDL scripts and create-drop (
spring.jpa.hibernate.ddl-auto
) shouldn't be activated for my tests - Good integration with the excellent Spring tests ecosystem
For this task, I found the awesome project: Test containers. The project describes itself as the following:
“Testcontainers is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.”
With this blog post, we'll use Testcontainers to write integration tests with JUnit using a real database (meaning not mocked or in-memory) for a Spring Boot application.
UPDATE: Time flies and a lot was introduced since I published this blog post. Therefore I added integration test examples for different combinations of JUnit 4 & 5 and Spring Boot versions.
Setup Testcontainers in Spring Boot project
For using this dependency you need to have Docker on your local machine/on your build server (Jenkins etc.). With Testcontainers you can use a @ClassRule
or @Rule
on each of your integration tests and define the Docker image for your test (valid for JUnit 4.12). For MySQL and PostgreSQL and there are already built-in solutions but you are free to use an image of your choice like the following:
1 2 3 4 5 6 7 8 | // generic container for self-defined Docker images @ClassRule public static GenericContainer redis = new GenericContainer("redis:3.0.6").withExposedPorts(6379); // built-in containers @ClassRule public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer().withPassword("inmemory") .withUsername("inmemory"); |
To run the integrations tests after your unit tests, simply add maven-failsafe-plugin
to your project. In addition, make sure your integration tests have IT
as a postfix:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>3.0.0-M4</version> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin> </plugins> </build> |
Basic application integration test with Testcontainers
Using: JUnit 4.12 and Spring Boot < 2.2.6
Let's start with the integration test each Spring Boot application contains out-of-the-box. This integration test verifies that Spring can create the context and start the application.
As our application requires a PostgreSQL to be available during startup, we can provide one using Testcontainers. Overriding the properties to use the PostgreSQL database spawned by Testcontainers is as easy as the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ContextConfiguration(initializers = IntegrationTest.Initializer.class) public class ApplicationIT { @ClassRule public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer().withPassword("inmemory") .withUsername("inmemory"); public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { TestPropertyValues values = TestPropertyValues.of( "spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(), "spring.datasource.password=" + postgreSQLContainer.getPassword(), "spring.datasource.username=" + postgreSQLContainer.getUsername() ); values.applyTo(configurableApplicationContext); } } @Ŧest public void contextLoads() { } } |
Furthermore, you can find a video tutorial for this setup here.
Basic application integration test with JUnit 5 and Spring Boot > 2.2.6
If your application uses JUnit 5, you can't use the @ClassRule
anymore. Fortunately, Testcontainers provides a solution to write tests with JUnit Jupiter:
1 2 3 4 5 6 | <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <version>${testcontainers.version}</version> <scope>test</scope> </dependency> |
With this dependency and a more recent version of Spring Boot (> 2.2.6) the basic integration test looks like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // JUnit 5 example with Spring Boot >= 2.2.6 @Testcontainers @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class ApplicationIT { @Container public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer() .withPassword("inmemory") .withUsername("inmemory"); @DynamicPropertySource static void postgresqlProperties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl); registry.add("spring.datasource.password", postgreSQLContainer::getPassword); registry.add("spring.datasource.username", postgreSQLContainer::getUsername); } @Test public void contextLoads() { } } |
In addition, you can find a video tutorial for this setup on YouTube.
Integration test with JUnit 5 and Spring Boot < 2.2.6
If your application makes use of JUnit 5 but is using a Spring Boot version < 2.2.6, you don't have access to the @DynamicPropertySource
feature.
A possible integration test to verify a REST API endpoint is working as expected looks like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | // JUnit 5 example with Spring Boot < 2.2.6 @Testcontainers @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @ContextConfiguration(initializers = DeletePersonIT.Initializer.class) public class DeletePersonIT { @Container public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer() .withPassword("inmemory") .withUsername("inmemory"); @Autowired private PersonRepository personRepository; @Autowired public TestRestTemplate testRestTemplate; public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { TestPropertyValues values = TestPropertyValues.of( "spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(), "spring.datasource.password=" + postgreSQLContainer.getPassword(), "spring.datasource.username=" + postgreSQLContainer.getUsername() ); values.applyTo(configurableApplicationContext); } } @Test @Sql("/testdata/FILL_FOUR_PERSONS.sql") public void testDeletePerson() { testRestTemplate.delete("/api/persons/1"); assertEquals(3, personRepository.findAll().size()); assertFalse(personRepository.findAll().contains("Phil")); } } |
You can find more integration test examples for this demo Spring Boot CRUD API application using PostgreSQL on GitHub.
Further integration test-related tutorials for Spring Boot:
- Spring Boot Functional Tests with Selenium and Testcontainers
- Spring Boot Integration Tests with WireMock and JUnit 5
Happy integration-testing with Spring Boot, Testcontainers and JUnit,
Phil
Hi! Thank you for an awesome article. It is really useful tips for anyone who wants to do integration testing for Spring Boot applications.
Actually, there is a really suitable open-source library for Spring Boot, which makes integration testing with containers even more easy – https://github.com/Playtika/testcontainers-spring-boot
Hi Oleksandr, thanks for the link!
[…] […]
[…] […]
[…] Ensuring your application is working properly is a critical part of continuous integration and delivery. Unit tests help you to test your methods’ business logic for both normal & edge cases. When it comes to guaranteeing that your users are able to correctly work with your application, we need something different. If your application exposes a frontend with user interaction, we can write functional tests to ensure different use cases are working. With this blog post, I’ll provide an example of how to write Functional Tests for Spring Boot applications using Selenium and Testcontainers. […]
[…] is now since 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 […]
[…] the processing. During your integration test you put a message into your queue (e.g. while using Testcontainers) and start the whole Spring context (@SpringBootTest). The lazy developer might simply add mocked […]
[…] For writing integration tests, you might want to include additional dependencies (e.g. WireMock, Testcontainers or Selenium) depending on your application […]
[…] use e.g. Testcontainers to create a PostgreSQL database for […]
[…] you are familiar with Tescontainers, you might wonder if we can utilize its WebDriver module for the web tests that we write with […]
[…] Write Spring Boot Integration Tests with Testcontainers (JUnit 4 & 5) […]