TestContainers Cheatsheet
Installation
Prerequisites
| Requirement | Version | Verification Command |
|---|
| Docker | Latest stable | docker --version |
| Java JDK | 8 or higher | java -version |
| Maven/Gradle | Latest | mvn -v or gradle -v |
Maven Dependencies
<!-- Core TestContainers -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
<!-- JUnit 5 Integration -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
<!-- Database Modules -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mongodb</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
Gradle Dependencies
// Kotlin DSL
testImplementation("org.testcontainers:testcontainers:1.19.3")
testImplementation("org.testcontainers:junit-jupiter:1.19.3")
testImplementation("org.testcontainers:postgresql:1.19.3")
testImplementation("org.testcontainers:mysql:1.19.3")
testImplementation("org.testcontainers:kafka:1.19.3")
// Groovy DSL
testImplementation 'org.testcontainers:testcontainers:1.19.3'
testImplementation 'org.testcontainers:junit-jupiter:1.19.3'
testImplementation 'org.testcontainers:postgresql:1.19.3'
| Platform | Installation Command |
|---|
| Ubuntu/Debian | curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh |
| macOS | brew install --cask docker |
| Windows | Download Docker Desktop from docker.com |
| Linux Post-Install | sudo usermod -aG docker $USER && newgrp docker |
Basic Commands
Container Lifecycle Operations
| Command | Description |
|---|
container.start() | Start a container instance |
container.stop() | Stop and remove the container |
container.isRunning() | Check if container is currently running |
container.getContainerId() | Get Docker container ID |
container.getDockerImageName() | Get the image name used |
container.getLogs() | Retrieve all container logs as string |
container.getHost() | Get host address (usually localhost) |
container.getMappedPort(int) | Get host port mapped to container port |
container.getExposedPorts() | List all exposed container ports |
container.getContainerInfo() | Get detailed container information |
Generic Container Creation
| Command | Description |
|---|
new GenericContainer("image:tag") | Create container from any Docker image |
.withExposedPorts(port1, port2) | Expose container ports to host |
.withEnv("KEY", "value") | Set environment variable |
.withCommand("cmd", "arg1") | Override container command |
.withLabel("key", "value") | Add Docker label to container |
.withNetworkMode("host") | Set container network mode |
.withPrivilegedMode() | Run container in privileged mode |
.withCreateContainerCmdModifier(cmd) | Modify container creation command |
.withStartupTimeout(Duration) | Set maximum startup wait time |
.withReuse(true) | Enable container reuse across tests |
Database Containers
| Command | Description |
|---|
new PostgreSQLContainer("postgres:15") | Create PostgreSQL container |
new MySQLContainer("mysql:8.0") | Create MySQL container |
new MongoDBContainer("mongo:6.0") | Create MongoDB container |
.withDatabaseName("dbname") | Set database name |
.withUsername("user") | Set database username |
.withPassword("pass") | Set database password |
.getJdbcUrl() | Get JDBC connection URL |
.getReplicaSetUrl() | Get MongoDB replica set URL (MongoDB) |
.withInitScript("init.sql") | Run SQL script on startup |
.withConfigurationOverride("path") | Override database configuration |
Advanced Usage
Wait Strategies
| Command | Description |
|---|
.waitingFor(Wait.forListeningPort()) | Wait until port is listening |
.waitingFor(Wait.forHttp("/health")) | Wait for HTTP endpoint to respond |
.waitingFor(Wait.forLogMessage(".*ready.*", 1)) | Wait for specific log message |
.waitingFor(Wait.forHealthcheck()) | Wait for Docker healthcheck to pass |
.waitingFor(Wait.forSuccessfulCommand("cmd")) | Wait for command to succeed |
.forStatusCode(200) | Specify expected HTTP status code |
.forStatusCodeMatching(code -> code < 500) | Custom status code matcher |
.withStartupTimeout(Duration.ofMinutes(2)) | Set startup timeout duration |
.withReadTimeout(Duration.ofSeconds(10)) | Set HTTP read timeout |
.forResponsePredicate(response -> true) | Custom response validation |
File and Volume Operations
| Command | Description |
|---|
.withFileSystemBind("host", "container") | Bind mount host directory |
.withFileSystemBind(path, target, BindMode.READ_ONLY) | Mount with specific mode |
.withClasspathResourceMapping("res", "path") | Mount classpath resource |
.copyFileToContainer(MountableFile, "path") | Copy file into container |
.withCopyFileToContainer(file, path) | Copy file during startup |
.withTmpFs(Map.of("/tmp", "rw")) | Mount tmpfs filesystem |
MountableFile.forClasspathResource("file") | Reference classpath file |
MountableFile.forHostPath("/path") | Reference host file system path |
BindMode.READ_WRITE | Allow read and write access |
BindMode.READ_ONLY | Allow only read access |
Networking
| Command | Description |
|---|
Network.newNetwork() | Create custom Docker network |
.withNetwork(network) | Attach container to network |
.withNetworkAliases("alias") | Set network alias for container |
.dependsOn(otherContainer) | Define container dependency |
.withExtraHost("hostname", "ip") | Add entry to /etc/hosts |
.getNetworkAliases() | Get all network aliases |
.withAccessToHost(true) | Allow container to access host |
Network.SHARED | Use shared network across tests |
.withNetworkMode("bridge") | Set specific network mode |
.getNetwork() | Get network container is attached to |
Container Execution
| Command | Description |
|---|
.execInContainer("cmd", "arg1") | Execute command in running container |
.execInContainer(Charset, "cmd") | Execute with specific charset |
result.getStdout() | Get standard output from execution |
result.getStderr() | Get standard error from execution |
result.getExitCode() | Get command exit code |
.copyFileFromContainer("path", consumer) | Copy file from container |
.followOutput(consumer) | Stream container logs |
new Slf4jLogConsumer(logger) | Log output to SLF4J logger |
new ToStringConsumer() | Capture output as string |
new WaitingConsumer() | Wait for specific output |
JUnit 5 Integration
| Command | Description |
|---|
@Testcontainers | Enable TestContainers extension |
@Container | Mark field as container to manage |
static @Container | Share container across all tests |
@DynamicPropertySource | Inject container properties |
registry.add("prop", container::getUrl) | Add dynamic property |
@BeforeAll | Setup containers before all tests |
@AfterAll | Cleanup containers after all tests |
@Nested | Group tests with shared containers |
Startables.deepStart(containers).join() | Start multiple containers in parallel |
.withReuse(true) | Reuse container across test runs |
Configuration
TestContainers Properties
Create testcontainers.properties in src/test/resources:
# Docker host configuration
docker.host=unix:///var/run/docker.sock
# docker.host=tcp://localhost:2375
# Container reuse (requires Docker labels support)
testcontainers.reuse.enable=true
# Image pull policy
testcontainers.image.pull.policy=DefaultPullPolicy
# Ryuk container (cleanup)
testcontainers.ryuk.disabled=false
testcontainers.ryuk.container.image=testcontainers/ryuk:0.5.1
# Checks
testcontainers.checks.disable=false
Docker Compose Integration
@Container
static DockerComposeContainer environment =
new DockerComposeContainer(new File("docker-compose.yml"))
.withExposedService("db", 5432)
.withExposedService("redis", 6379)
.withLocalCompose(true)
.withPull(true)
.waitingFor("db", Wait.forHealthcheck());
Custom Container Configuration
public class CustomPostgreSQLContainer extends PostgreSQLContainer<CustomPostgreSQLContainer> {
private static final String IMAGE_VERSION = "postgres:15-alpine";
public CustomPostgreSQLContainer() {
super(IMAGE_VERSION);
this.withDatabaseName("testdb")
.withUsername("testuser")
.withPassword("testpass")
.withInitScript("init.sql")
.withCommand("postgres -c max_connections=200");
}
}
Spring Boot Integration
@SpringBootTest
@Testcontainers
class ApplicationTests {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")
.withDatabaseName("testdb");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
registry.add("spring.jpa.hibernate.ddl-auto", () -> "create-drop");
}
@Test
void contextLoads() {
// Test with real database
}
}
Common Use Cases
Use Case 1: PostgreSQL Integration Test
@Testcontainers
@SpringBootTest
class UserRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test")
.withInitScript("schema.sql");
@DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired
private UserRepository userRepository;
@Test
void shouldSaveAndFindUser() {
User user = new User("john@example.com", "John Doe");
userRepository.save(user);
Optional<User> found = userRepository.findByEmail("john@example.com");
assertTrue(found.isPresent());
assertEquals("John Doe", found.get().getName());
}
}
Use Case 2: Kafka Message Processing Test
@Testcontainers
class KafkaIntegrationTest {
@Container
static KafkaContainer kafka = new KafkaContainer(
DockerImageName.parse("confluentinc/cp-kafka:7.5.0")
);
@DynamicPropertySource
static void kafkaProperties(DynamicPropertyRegistry registry) {
registry.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
}
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
@Autowired
private MessageListener messageListener;
@Test
void shouldProcessMessage() throws Exception {
String topic = "test-topic";
String message = "Hello Kafka!";
kafkaTemplate.send(topic, message);
// Wait for message processing
await().atMost(Duration.ofSeconds(10))
.until(() -> messageListener.getReceivedMessages().size() == 1);
assertEquals(message, messageListener.getReceivedMessages().get(0));
}
}
Use Case 3: Multi-Container Microservices Test
@Testcontainers
class MicroservicesIntegrationTest {
static Network network = Network.newNetwork();
@Container
static PostgreSQLContainer<?> database = new PostgreSQLContainer<>("postgres:15-alpine")
.withNetwork(network)
.withNetworkAliases("database")
.withDatabaseName("orders");
@Container
static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
.withNetwork(network)
.withNetworkAliases("redis")
.withExposedPorts(6379);
@Container
static GenericContainer<?> orderService = new GenericContainer<>("order-service:latest")
.withNetwork(network)
.withExposedPorts(8080)
.withEnv("DATABASE_URL", "jdbc:postgresql://database:5432/orders")
.withEnv("REDIS_HOST", "redis")
.dependsOn(database, redis)
.waitingFor(Wait.forHttp("/actuator/health").forStatusCode(200));
@Test
void shouldCreateOrder() {
String baseUrl = "http://" + orderService.getHost() + ":"
+ orderService.getMappedPort(8080);
// Make HTTP request to order service
RestTemplate restTemplate = new RestTemplate();
Order order = new Order("item-123", 2);
ResponseEntity<Order> response = restTemplate.postForEntity(
baseUrl + "/orders", order, Order.class
);
assertEquals(HttpStatus.CREATED, response.getStatusCode());
assertNotNull(response.getBody().getId());
}
}
Use Case 4: MongoDB Integration Test
@Testcontainers
@DataMongoTest
class ProductRepositoryTest {
@Container
static MongoDBContainer mongodb = new MongoDBContainer("mongo:6.0")
.withExposedPorts(27017);
@DynamicPropertySource
static void mongoProperties(DynamicPropertyRegistry registry) {
registry.add("spring.data.mongodb.uri", mongodb::getReplicaSetUrl);
}
@Autowired
private ProductRepository productRepository;
@Test
void shouldFindProductsByCategory() {
// Insert test data
productRepository.save(new Product("Laptop", "Electronics", 999.99));
productRepository.save(new Product("Mouse", "Electronics", 29.99));
productRepository.save(new Product("Desk", "Furniture", 299.99));
// Query by category
List<Product> electronics = productRepository.findByCategory("Electronics");
assertEquals(2, electronics.size());
assertTrue(electronics.stream()
.allMatch(p -> p.getCategory().equals("Electronics")));
}
}
Use Case 5: Docker Compose Test Environment
@Testcontainers
class FullStackIntegrationTest {
@Container
static DockerComposeContainer<?> environment = new DockerComposeContainer<>(
new File("src/test/resources/docker-compose-test.yml")
)
.withExposedService("api", 8080,
Wait.forHttp("/health").forStatusCode(200))
.withExposedService("postgres", 5432,
Wait.forListeningPort())
.withExposedService("redis", 6379)
.withLocalCompose(true);
@Test
void shouldConnectToAllServices() {
String apiHost = environment.getServiceHost("api", 8080);
Integer apiPort = environment.getServicePort("api", 8080);
String apiUrl = "http://" + apiHost + ":" + apiPort;
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.getForEntity(
apiUrl + "/health", String.class
);
assertEquals(HttpStatus.OK, response.getStatusCode());
}
}
Best Practices
Container Management
- Use static containers for test classes: Share containers across all test methods to reduce startup time and resource usage
- Implement container reuse: Enable
testcontainers.reuse.enable=true for faster local development test cycles
- Clean up resources: Use
@AfterAll or try-with-resources when manually managing containers outside JUnit integration
- Use specific image tags: Avoid
latest tag; use specific versions like postgres:15-alpine for reproducible tests
- Parallel container startup: Use
Startables.deepStart() to start multiple independent containers simultaneously
- Minimize container restarts: Use class-level
@Container instead of method-level when possible
- Choose lightweight images: Prefer Alpine-based images (e.g.,
postgres:15-alpine) to reduce pull time and disk usage
- Implement wait strategies wisely: Use appropriate wait strategies to avoid unnecessary delays while ensuring container readiness
Testing Strategy
- Test against production-like dependencies: Use real database engines and message brokers instead of embedded versions
- Isolate test data: Ensure each test creates and cleans up its own data to prevent test interdependencies
- Use network aliases: Create custom networks and use meaningful aliases for inter-container communication
- Implement health checks: Always configure proper wait strategies to ensure containers are fully ready before tests execute
CI/CD Integration
- Verify Docker availability: Ensure CI environment has Docker installed and accessible
- Configure timeouts appropriately: Set realistic startup timeouts considering CI environment performance
- Use image caching: Configure CI to cache Docker images to speed up subsequent runs
- Monitor resource usage: Be aware of memory and CPU limits in CI environments; adjust container configurations accordingly
Security and Maintenance
- Keep TestContainers updated: Regularly update to latest versions for security patches and new features
- Use official images: Prefer official Docker images from verified publishers
- Avoid privileged mode: Only use
.withPrivilegedMode() when absolutely necessary
- Review exposed ports: Only expose ports that are actually needed for testing
Troubleshooting
| Issue | Solution |
|---|
| Container fails to start: “Cannot connect to Docker daemon” | Ensure Docker is running: docker ps. On Linux, add user to docker group: sudo usermod -aG docker $USER |
| Tests timeout during container startup | Increase timeout: .withStartupTimeout(Duration.ofMinutes(5)) or check Docker resources (CPU/Memory) |
| Port binding conflicts: “Address already in use” | Use dynamic ports: container.getMappedPort() instead of fixed ports, or stop conflicting services |
| Image pull fails: “manifest unknown” | Verify image name and tag exist on Docker Hub. Use specific tags instead of latest |
| Container starts but tests fail to connect | Check wait strategy is appropriate: add .waitingFor(Wait.forListeningPort()) or .waitingFor(Wait.forHealthcheck()) |
| Out of memory errors during tests | Reduce number of parallel containers, increase Docker memory limit in Docker Desktop settings, or use .withReuse(true) |
| ”Ryuk container not found” warnings | Disable Ryuk if needed: set testcontainers.ryuk.disabled=true in properties (not recommended for production) |
| Tests work locally but fail in CI | Ensure CI has Docker installed, check timeout settings, verify network connectivity, review CI environment resource limits |
| Database connection refused | Wait for database to be ready: use .waitingFor(Wait.forListeningPort()) and verify JDBC URL with container.getJdbcUrl() |
| Container logs show errors but tests pass | Enable log following: container.followOutput(new Slf4jLogConsumer(log)) to debug container issues |
| Slow test execution | Use container reuse, share containers at class level, start containers in parallel with Startables.deepStart() |
| ”No such file or directory” when mounting volumes | Use absolute paths or MountableFile.forClasspathResource() for classpath resources |
| Network communication between containers fails | Create custom network: Network.newNetwork() and attach containers with .withNetwork(network) and .withNetworkAliases() |
| TestContainers properties not being read | Ensure testcontainers.properties is in src/test/resources and properly formatted |
| Container cleanup not happening | Check Ryuk container is running: `docker ps |
Available Modules
| Module | Maven Artifact | Description |
|---|
| PostgreSQL | testcontainers-postgresql | PostgreSQL database containers |
| MySQL | testcontainers-mysql | MySQL database containers |
| MongoDB | testcontainers-mongodb | MongoDB document database |
| Kafka | testcontainers-kafka | Apache Kafka message broker |
| Redis | testcontainers-redis | Redis in-memory data store |
| Elasticsearch | testcontainers-elasticsearch | Elasticsearch search engine |
| Cassandra | testcontainers-cassandra | Cassandra NoSQL database |
| RabbitMQ | testcontainers-rabbitmq | RabbitMQ message broker |
| Selenium | testcontainers-selenium | Browser automation containers |
| LocalStack | testcontainers-localstack | AWS services emulation |