Zum Inhalt springen

TestContainers Cheatsheet

TestContainers Cheatsheet

Installation

Prerequisites

RequirementVersionVerification Command
DockerLatest stabledocker --version
Java JDK8 or higherjava -version
Maven/GradleLatestmvn -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'

Docker Setup by Platform

PlatformInstallation Command
Ubuntu/Debiancurl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
macOSbrew install --cask docker
WindowsDownload Docker Desktop from docker.com
Linux Post-Installsudo usermod -aG docker $USER && newgrp docker

Basic Commands

Container Lifecycle Operations

CommandDescription
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

CommandDescription
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

CommandDescription
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

CommandDescription
.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

CommandDescription
.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_WRITEAllow read and write access
BindMode.READ_ONLYAllow only read access

Networking

CommandDescription
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.SHAREDUse shared network across tests
.withNetworkMode("bridge")Set specific network mode
.getNetwork()Get network container is attached to

Container Execution

CommandDescription
.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

CommandDescription
@TestcontainersEnable TestContainers extension
@ContainerMark field as container to manage
static @ContainerShare container across all tests
@DynamicPropertySourceInject container properties
registry.add("prop", container::getUrl)Add dynamic property
@BeforeAllSetup containers before all tests
@AfterAllCleanup containers after all tests
@NestedGroup 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

Performance Optimization

  • 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

IssueSolution
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 startupIncrease 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 connectCheck wait strategy is appropriate: add .waitingFor(Wait.forListeningPort()) or .waitingFor(Wait.forHealthcheck())
Out of memory errors during testsReduce number of parallel containers, increase Docker memory limit in Docker Desktop settings, or use .withReuse(true)
”Ryuk container not found” warningsDisable Ryuk if needed: set testcontainers.ryuk.disabled=true in properties (not recommended for production)
Tests work locally but fail in CIEnsure CI has Docker installed, check timeout settings, verify network connectivity, review CI environment resource limits
Database connection refusedWait for database to be ready: use .waitingFor(Wait.forListeningPort()) and verify JDBC URL with container.getJdbcUrl()
Container logs show errors but tests passEnable log following: container.followOutput(new Slf4jLogConsumer(log)) to debug container issues
Slow test executionUse container reuse, share containers at class level, start containers in parallel with Startables.deepStart()
”No such file or directory” when mounting volumesUse absolute paths or MountableFile.forClasspathResource() for classpath resources
Network communication between containers failsCreate custom network: Network.newNetwork() and attach containers with .withNetwork(network) and .withNetworkAliases()
TestContainers properties not being readEnsure testcontainers.properties is in src/test/resources and properly formatted
Container cleanup not happeningCheck Ryuk container is running: `docker ps

Available Modules

ModuleMaven ArtifactDescription
PostgreSQLtestcontainers-postgresqlPostgreSQL database containers
MySQLtestcontainers-mysqlMySQL database containers
MongoDBtestcontainers-mongodbMongoDB document database
Kafkatestcontainers-kafkaApache Kafka message broker
Redistestcontainers-redisRedis in-memory data store
Elasticsearchtestcontainers-elasticsearchElasticsearch search engine
Cassandratestcontainers-cassandraCassandra NoSQL database
RabbitMQtestcontainers-rabbitmqRabbitMQ message broker
Seleniumtestcontainers-seleniumBrowser automation containers
LocalStacktestcontainers-localstackAWS services emulation