JUnit Jupiter extension that enables automatic lifecycle management of Docker containers in JUnit 5 tests
npx @tessl/cli install tessl/maven-org-testcontainers--junit-jupiter@1.21.0A JUnit Jupiter extension that enables automatic lifecycle management of Docker containers in JUnit 5 tests. The extension provides annotations to seamlessly integrate container-based testing into the JUnit Jupiter framework, supporting both static containers (shared between test methods) and instance containers (started/stopped per test method).
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.21.3</version>
<scope>test</scope>
</dependency>testImplementation 'org.testcontainers:junit-jupiter:1.21.3'import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.EnabledIfDockerAvailable;import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Testcontainers
class MyTestcontainersTests {
// Shared between test methods - started once, stopped after all tests
@Container
private static final MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0");
// Started before and stopped after each test method
@Container
private PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@Test
void testContainersAreRunning() {
assertTrue(mysql.isRunning());
assertTrue(postgres.isRunning());
}
}The JUnit Jupiter extension integrates with JUnit 5's extension model through several key components:
@Container annotated fields and manages their lifecycleBeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback, and ExecutionCondition for precise container lifecycle controlTestLifecycleAware containers to provide test context informationAutomatically manages Docker container startup and shutdown through the @Testcontainers annotation and @Container field annotation.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(TestcontainersExtension.class)
@Inherited
public @interface Testcontainers {
/**
* Whether tests should be disabled (rather than failing) when Docker is not available.
* @return if the tests should be disabled when Docker is not available
*/
boolean disabledWithoutDocker() default false;
/**
* Whether containers should start in parallel.
* @return if the containers should start in parallel
*/
boolean parallel() default false;
}
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Container {
}Container Lifecycle Rules:
@Container: Shared between all test methods in the class. Started once before any test method executes, stopped after the last test method completes.@Container: Restarted for each individual test method. Started before each test method, stopped after each test method.@Container must implement org.testcontainers.lifecycle.Startable interface.Usage Examples:
@Testcontainers
class ContainerLifecycleExample {
// Shared container - expensive to start, reused across tests
@Container
static final PostgreSQLContainer<?> sharedPostgres =
new PostgreSQLContainer<>("postgres:13");
// Per-test container - fresh instance for each test
@Container
RedisContainer redis = new RedisContainer("redis:6-alpine");
@Test
void testWithFreshRedis() {
// redis container is started fresh for this test
String redisUrl = redis.getRedisURI();
// ... test logic
}
@Test
void testWithSharedPostgres() {
// sharedPostgres container was started once and is reused
String jdbcUrl = sharedPostgres.getJdbcUrl();
// ... test logic
}
}Provides Docker availability detection and conditional test execution capabilities.
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ExtendWith(EnabledIfDockerAvailableCondition.class)
public @interface EnabledIfDockerAvailable {
}Docker Availability Options:
Fail when Docker unavailable (default behavior):
@Testcontainers
class MyTests {
// Tests will fail if Docker is not available
}Disable tests when Docker unavailable:
@Testcontainers(disabledWithoutDocker = true)
class MyTests {
// Tests will be skipped if Docker is not available
}Method-level Docker requirement:
class MyTests {
@Test
@EnabledIfDockerAvailable
void testRequiringDocker() {
// This test only runs if Docker is available
}
@Test
void testNotRequiringDocker() {
// This test always runs
}
}The extension integrates with JUnit Jupiter's lifecycle and provides access to the underlying extension functionality.
public class TestcontainersExtension
implements BeforeEachCallback, BeforeAllCallback, AfterEachCallback, AfterAllCallback, ExecutionCondition {
/**
* Check if Docker is available on the system
* @return true if Docker client can be created and accessed
*/
public boolean isDockerAvailable();
}Parallel Container Startup:
@Testcontainers(parallel = true)
class ParallelStartupExample {
@Container
static final PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13");
@Container
static final MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0");
@Container
static final RedisContainer redis = new RedisContainer("redis:6-alpine");
// All three containers will start in parallel, reducing total startup time
}Test Lifecycle Integration:
For containers implementing org.testcontainers.lifecycle.TestLifecycleAware, the extension provides test context:
// Container receives test lifecycle callbacks
public class MyCustomContainer extends GenericContainer<MyCustomContainer>
implements TestLifecycleAware {
@Override
public void beforeTest(TestDescription description) {
// Called before each test method
// description provides test ID and filesystem-friendly name
}
@Override
public void afterTest(TestDescription description, Optional<Throwable> throwable) {
// Called after each test method
// throwable contains test failure information if test failed
}
}Common Configuration Errors:
ExtensionConfigurationException: Thrown when @Container field does not implement Startable interfaceExtensionConfigurationException: Thrown when @Container field is null (containers must be initialized)ExtensionConfigurationException: Thrown when @Testcontainers annotation is not found on class hierarchyDocker Availability Handling:
disabledWithoutDocker = false (default): Tests fail with Docker connection errors if Docker is unavailabledisabledWithoutDocker = true: Tests are marked as disabled/skipped if Docker is unavailable@EnabledIfDockerAvailable: Individual tests/classes are skipped if Docker is unavailableJUnit Jupiter: Requires JUnit Jupiter 5.x (junit-jupiter-api dependency) Testcontainers Core: Depends on org.testcontainers:testcontainers core library Docker: Requires Docker daemon running and accessible to the test process
Inheritance Support: The @Testcontainers annotation is @Inherited, so subclasses automatically inherit the extension behavior from parent test classes.