Testing Spring Boot applications with TestContainers revisted 2020
It's been a year since I wrote a quick series of posts about testing Spring Boot applications with TestContainers.
- Part One - Data access layer integration tests
- Part Two - Application integration tests
- Part Three - UI tests
Since then, there have a been a few changes in the tools, frameworks and documentation that have outdated the information in those posts.
- Spring Boot announced default support for Junit 5
- Spring Boot documentation for ConfigFileApplicationContextInitializer
- TestContainers support for JUnit 5
- @DynamicPropertySource in Spring Boot 2.2.6
So this post, will cover the changes required to upgrade.
testcontainers-demo
We will continue to use the testcontainers-demo application as the System under test (SUT). The application routes notification messages from a JMS Queue to a RabbitMQ exchange, storing each notification in a Postgres database. This application also provides a web interface to see a list of all the messages that are routed by the application. The source code from the previous posts will now be availble on a spring_boot_2_1_x_junit_4 branch.
Upgrade Spring Boot
Spring Boot is now on the 2.2.x branch so first change is to upgrade the version in the maven pom.xml. This unlocks some of the new features that support TestContainers usage.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath />
</parent>
Upgrade TestContainers versions
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.13.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.13.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>selenium</artifactId>
<version>1.13.0</version>
<scope>test</scope>
</dependency>
Upgrade tests from JUnit 4 to JUnit 5
TestContainers requires new JUnit 5 jars.
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.13.0</version>
<scope>test</scope>
</dependency>
Typically the changes to be made are to replace the old org.junit.*
packages for org.junit.jupiter.api.*
. We no longer have a requirement to expose public methods for JUnit 5. JUnit 5 also drops the org.junit.Assert.assertThat
type which we can replace with Hamcrest type org.hamcrest.MatcherAssert.assertThat
.
package com.robintegg.testcontainersdemo.routing;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import org.junit.jupiter.api.Test;
class NotificationRepositoryTest {
@Test
void shouldStoreEachNotification() {
...
// then
assertThat(count, is(2L));
}
}
Replace ApplicationContextInitializer configuration with DynamicPropertySource
Since the inclusion of Spring Framework 5.2.5, the configuration of the Spring Boot environment from TestContainers is now simpler.
Replace:
@ContextConfiguration(initializers = {RoutingTest.Initializer.class}, classes = RabbitMqTestConfiguration.class)
@Testcontainers
class RoutingTest {
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
DemoApplicationTestPropertyValues.using(postgreSQLContainer, activeMQContainer, rabbitMQContainer)
.applyTo(configurableApplicationContext.getEnvironment());
}
}
}
With:
class RoutingTest {
@DynamicPropertySource
static void registerDynamicProperties(DynamicPropertyRegistry registry) {
DemoApplicationTestPropertyValues.populateRegistryFromPContainers(registry, postgreSQLContainer, activeMQContainer, rabbitMQContainer);
}
}
public class DemoApplicationTestPropertyValues {
public static void populateRegistryFromContainers(DynamicPropertyRegistry registry, PostgreSQLContainer<?> postgreSQLContainer ... ) {
registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
...
}
}
Configure logback
As per the recommended logback configuration
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
<logger name="org.testcontainers" level="INFO"/>
<logger name="com.github.dockerjava" level="WARN"/>
</configuration>