Jupiter / JUnit 5
While Testcontainers is tightly coupled with the JUnit 4.x rule API, this module provides an API that is based on the JUnit Jupiter extension model.
The extension supports two modes:
- containers that are restarted for every test method
- containers that are shared between all methods of a test class
Note that Jupiter/JUnit 5 integration is packaged as a separate library JAR; see below for details.
Extension
Jupiter integration is provided by means of the @Testcontainers annotation.
The extension finds all fields that are annotated with @Container and calls their container lifecycle
methods (methods on the Startable interface). Containers declared as static fields will be shared between test
methods. They will be started only once before any test method is executed and stopped after the last test method has
executed. Containers declared as instance fields will be started and stopped for every test method.
Note: This extension has only been tested with sequential test execution. Using it with parallel test execution is unsupported and may have unintended side effects.
Example:
@Testcontainers
class MixedLifecycleTests {
// will be shared between test methods
@Container
private static final MySQLContainer MY_SQL_CONTAINER = new MySQLContainer("mysql:8.0.36");
// will be started before and stopped after each test method
@Container
private PostgreSQLContainer postgresqlContainer = new PostgreSQLContainer("postgres:9.6.12")
.withDatabaseName("foo")
.withUsername("foo")
.withPassword("secret");
@Test
void test() {
assertThat(MY_SQL_CONTAINER.isRunning()).isTrue();
assertThat(postgresqlContainer.isRunning()).isTrue();
}
}
Examples
To use the Testcontainers extension annotate your test class with @Testcontainers.
Restarted containers
To define a restarted container, define an instance field inside your test class and annotate it with
the @Container annotation.
@Testcontainers
class TestcontainersNestedRestartedContainerTests {
@Container
private final GenericContainer<?> topLevelContainer = new GenericContainer<>(JUnitJupiterTestImages.HTTPD_IMAGE)
.withExposedPorts(80);
⋯
@Test
void top_level_container_should_be_running() {
assertThat(topLevelContainer.isRunning()).isTrue();
⋯
}
@Nested
class NestedTestCase {
@Container
private final GenericContainer<?> nestedContainer = new GenericContainer<>(JUnitJupiterTestImages.HTTPD_IMAGE)
.withExposedPorts(80);
@Test
void both_containers_should_be_running() {
// top level container is restarted for nested methods
assertThat(topLevelContainer.isRunning()).isTrue();
// nested containers are only available inside their nested class
assertThat(nestedContainer.isRunning()).isTrue();
⋯
}
⋯
}
}
Shared containers
Shared containers are defined as static fields in a top level test class and have to be annotated with @Container.
Note that shared containers can't be declared inside nested test classes.
This is because nested test classes have to be defined non-static and can't therefore have static fields.
Singleton containers
Note that the singleton container pattern is also an option when using JUnit 5.
Named Container Providers
The @ContainerProvider and @ContainerConfig annotations provide a declarative way to define and reuse containers across multiple tests and test classes, eliminating the need for manual singleton patterns.
Overview
Container providers allow you to: - Define containers once and reference them by name - Share containers across multiple test classes - Control container lifecycle (class-scoped or global) - Choose between reusing instances or creating new ones per test - Inject containers as test method parameters
Basic Usage
Define a container provider method using @ContainerProvider:
@Testcontainers
class MyIntegrationTests {
@ContainerProvider(name = "redis", scope = Scope.GLOBAL)
public GenericContainer<?> createRedis() {
return new GenericContainer<>("redis:6.2")
.withExposedPorts(6379);
}
@Test
@ContainerConfig(name = "redis")
void testWithRedis() {
// Redis container is automatically started
}
}
Container Scopes
Containers can have two scopes:
Scope.CLASS: Container is shared within a single test class and stopped after all tests in that class completeScope.GLOBAL: Container is shared across all test classes and stopped at the end of the test suite
@ContainerProvider(name = "database", scope = Scope.GLOBAL)
public PostgreSQLContainer<?> createDatabase() {
return new PostgreSQLContainer<>("postgres:14");
}
@ContainerProvider(name = "cache", scope = Scope.CLASS)
public GenericContainer<?> createCache() {
return new GenericContainer<>("redis:6.2");
}
Parameter Injection
Containers can be injected as test method parameters:
@Test
@ContainerConfig(name = "redis", injectAsParameter = true)
void testWithInjection(GenericContainer<?> redis) {
String host = redis.getHost();
int port = redis.getFirstMappedPort();
// Use container...
}
Creating New Instances
By default, containers are reused. Use needNewInstance = true for test isolation:
@Test
@ContainerConfig(name = "database", needNewInstance = false)
void testSharedDatabase() {
// Reuses existing database container
}
@Test
@ContainerConfig(name = "database", needNewInstance = true)
void testIsolatedDatabase() {
// Gets a fresh database container
}
Cross-Class Container Sharing
Containers can be shared across multiple test classes using inheritance:
abstract class BaseIntegrationTest {
@ContainerProvider(name = "sharedDb", scope = Scope.GLOBAL)
public PostgreSQLContainer<?> createDatabase() {
return new PostgreSQLContainer<>("postgres:14");
}
}
@Testcontainers
class UserServiceTests extends BaseIntegrationTest {
@Test
@ContainerConfig(name = "sharedDb", injectAsParameter = true)
void testUserService(PostgreSQLContainer<?> db) {
// Uses shared database
}
}
@Testcontainers
class OrderServiceTests extends BaseIntegrationTest {
@Test
@ContainerConfig(name = "sharedDb", injectAsParameter = true)
void testOrderService(PostgreSQLContainer<?> db) {
// Reuses the same database instance
}
}
Multiple Providers
A test class can define multiple container providers:
@Testcontainers
class MultiContainerTests {
@ContainerProvider(name = "postgres", scope = Scope.GLOBAL)
public PostgreSQLContainer<?> createPostgres() {
return new PostgreSQLContainer<>("postgres:14");
}
@ContainerProvider(name = "redis", scope = Scope.GLOBAL)
public GenericContainer<?> createRedis() {
return new GenericContainer<>("redis:6.2");
}
@Test
@ContainerConfig(name = "postgres", injectAsParameter = true)
void testDatabase(PostgreSQLContainer<?> db) {
// Use postgres
}
@Test
@ContainerConfig(name = "redis", injectAsParameter = true)
void testCache(GenericContainer<?> cache) {
// Use redis
}
}
Static vs Instance Provider Methods
Provider methods can be either static or instance methods:
// Static provider - no test instance needed
@ContainerProvider(name = "static", scope = Scope.GLOBAL)
public static GenericContainer<?> createStatic() {
return new GenericContainer<>("httpd:2.4");
}
// Instance provider - can access test instance fields
@ContainerProvider(name = "instance", scope = Scope.CLASS)
public GenericContainer<?> createInstance() {
return new GenericContainer<>("httpd:2.4");
}
Compatibility with @Container
Named providers work alongside traditional @Container fields:
@Testcontainers
class MixedApproachTests {
@Container
private static final GenericContainer<?> TRADITIONAL =
new GenericContainer<>("httpd:2.4");
@ContainerProvider(name = "provided", scope = Scope.CLASS)
public GenericContainer<?> createProvided() {
return new GenericContainer<>("redis:6.2");
}
@Test
void testTraditional() {
// Use TRADITIONAL container
}
@Test
@ContainerConfig(name = "provided")
void testProvided() {
// Use provided container
}
}
Limitations
Since this module has a dependency onto JUnit Jupiter and on Testcontainers core, which has a dependency onto JUnit 4.x, projects using this module will end up with both, JUnit Jupiter and JUnit 4.x in the test classpath.
This extension has only been tested with sequential test execution. Using it with parallel test execution is unsupported and may have unintended side effects.
Adding Testcontainers JUnit 5 support to your project dependencies
Add the following dependency to your pom.xml/build.gradle file:
testImplementation "org.testcontainers:testcontainers-junit-jupiter:2.0.1"
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<version>2.0.1</version>
<scope>test</scope>
</dependency>