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:
// 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:
<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:
@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() {
}
}
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:
<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:
// 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() {
}
}
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:
// 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
Top comments (1)
Thanks for this, this helped me to get Spring Boot 2.4.5 working with Testcontainers and Jupiter!