This test shows how to use Testcontainers in Spring Boot. It requires Spring Boot 3.1 and Docker.
My test environment is following.
- Spring Boot 3.2.1
- Java 17
- Docker 24.0.6
In this document, we will use Redis as example. You can use other service like MySQL.
There are serveral ways to use Testcontainers. At first, you have to add Testconatiners dependency to your project.
testImplementation 'org.springframework.boot:spring-boot-testcontainers'
Simplest way is using static initialize block to start container.
static {
final GenericContainer<?> REDIS_CONTAINER =
new GenericContainer<>(DockerImageName.parse("redis:latest")).withExposedPorts(6379);
REDIS_CONTAINER.start();
System.setProperty("spring.data.redis.host", REDIS_CONTAINER.getHost());
System.setProperty("spring.data.redis.port", REDIS_CONTAINER.getMappedPort(6379).toString());
}
System.setProperty
is needed to set host and port of container to Spring Boot application.
With Testcontainers JUnit, you can manage lifecycle of container using annotation. Before using this, you have to add dependency.
testImplementation 'org.testcontainers:junit-jupiter'
Next, add @Testcontainers
annotation which has JUnit extension for Testcontainers to your test class.
@Testcontainers
class TestcontainersTest{
/* do your test */
}
And then, you can use @Container
annotation to manage lifecycle of container.
@Container
static final GenericContainer<?> REDIS_CONTAINER =
new GenericContainer<>(DockerImageName.parse("redis:latest")).withExposedPorts(6379);
If annotation is used in static field, container will be started at beforeAll()
cycle and stopped at afterAll()
cycle.
Otherwise, if annotation is used in instance field, container will be started at beforeEach()
cycle and stopped at
afterEach()
cycle.
@DynamicPropertySource
static void setProperties(DynamicPropertyRegistry registry) {
registry.add("spring.data.redis.host", REDIS_CONTAINER::getHost);
registry.add("spring.data.redis.port", REDIS_CONTAINER::getFirstMappedPort);
}
Finally, you have to set host and port of container to Spring Boot application.
@DynamicPropertySource
can be replaced with @ServiceConnection
since Spring Boot 3.1.
@Container
@ServiceConnection
static final GenericContainer<?> REDIS_CONTAINER =
new GenericContainer<>(DockerImageName.parse("redis:latest")).withExposedPorts(6379);
If you want to specify test that uses Testcontainers, define custom your annotation using @ExtendWith
.
By implementing interface extending Extension
, you can manage JUnit lifecycle. In this case, we will use @BeforeAll
for starting container each test instance.
public class RedisContainerExtension implements BeforeAllCallback {
static final GenericContainer<?> REDIS_CONTAINER =
new GenericContainer<>(DockerImageName.parse("redis:latest")).withExposedPorts(6379);
@Override
public void beforeAll(final ExtensionContext context) throws Exception {
if (REDIS_CONTAINER.isRunning()) {
return;
}
REDIS_CONTAINER.start();
System.setProperty("spring.data.redis.host", REDIS_CONTAINER.getHost());
System.setProperty(
"spring.data.redis.port", REDIS_CONTAINER.getMappedPort(6379).toString());
}
}
You can add your own lifecycle callback by implementing Callback, for instance, rollback table.
@Override
public void afterEach(final ExtensionContext context) throws Exception {
/* do rollback container (e.g. truncate table) */
}
It is advisable to avoid using GenericContainer.stop() as it halts the container immediately, potentially causing issues for components dependent on the container
And then, define annotation using @ExtendWith
.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(RedisContainerExtension.class)
public @interface RedisTest {}
Finally, you can use annotation to specify test that uses Testcontainers.
@RedisTest
class RedisServiceTest{
/* do your test */
}
Spring Boot Testcontainers provides modules for several service.
Testcontainers provides testcontainers-mysql
module.
testImplementation "org.testcontainers:mysql:1.19.3"
@Container
static final MySQLContainer<?> MYSQL_CONTAINER = new MySQLContainer<>("mysql:latest");