Saltar a contenido

TestContainers

_

TestContainers Cheatsheet

Instalación

Prerequisites

Requirement Version Verification Command
Docker Latest stable INLINE_CODE_12
Java JDK 8 or higher INLINE_CODE_13
Maven/Gradle Latest INLINE_CODE_14 or INLINE_CODE_15

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

Platform Installation Command
Ubuntu/Debian INLINE_CODE_16
macOS INLINE_CODE_17
Windows Download Docker Desktop from docker.com
Linux Post-Install INLINE_CODE_18

Comandos básicos

Container Lifecycle Operations

Command Description
INLINE_CODE_19 Start a container instance
INLINE_CODE_20 Stop and remove the container
INLINE_CODE_21 Check if container is currently running
INLINE_CODE_22 Get Docker container ID
INLINE_CODE_23 Get the image name used
INLINE_CODE_24 Retrieve all container logs as string
INLINE_CODE_25 Get host address (usually localhost)
INLINE_CODE_26 Get host port mapped to container port
INLINE_CODE_27 List all exposed container ports
INLINE_CODE_28 Get detailed container information

Generic Container Creation

Command Description
INLINE_CODE_29 Create container from any Docker image
INLINE_CODE_30 Expose container ports to host
INLINE_CODE_31 Set environment variable
INLINE_CODE_32 Override container command
INLINE_CODE_33 Add Docker label to container
INLINE_CODE_34 Set container network mode
INLINE_CODE_35 Run container in privileged mode
INLINE_CODE_36 Modify container creation command
INLINE_CODE_37 Set maximum startup wait time
INLINE_CODE_38 Enable container reuse across tests

Database Containers

Command Description
INLINE_CODE_39 Create PostgreSQL container
INLINE_CODE_40 Create MySQL container
INLINE_CODE_41 Create MongoDB container
INLINE_CODE_42 Set database name
INLINE_CODE_43 Set database username
INLINE_CODE_44 Set database password
INLINE_CODE_45 Get JDBC connection URL
INLINE_CODE_46 Get MongoDB replica set URL (MongoDB)
INLINE_CODE_47 Run SQL script on startup
INLINE_CODE_48 Override database configuration

Advanced Usage

Wait Strategies

Command Description
INLINE_CODE_49 Wait until port is listening
INLINE_CODE_50 Wait for HTTP endpoint to respond
INLINE_CODE_51 Wait for specific log message
INLINE_CODE_52 Wait for Docker healthcheck to pass
INLINE_CODE_53 Wait for command to succeed
INLINE_CODE_54 Specify expected HTTP status code
INLINE_CODE_55 Custom status code matcher
INLINE_CODE_56 Set startup timeout duration
INLINE_CODE_57 Set HTTP read timeout
INLINE_CODE_58 Custom response validation

Operaciones de archivo y volumen

Command Description
INLINE_CODE_59 Bind mount host directory
INLINE_CODE_60 Mount with specific mode
INLINE_CODE_61 Mount classpath resource
INLINE_CODE_62 Copy file into container
INLINE_CODE_63 Copy file during startup
INLINE_CODE_64 Mount tmpfs filesystem
INLINE_CODE_65 Reference classpath file
INLINE_CODE_66 Reference host file system path
INLINE_CODE_67 Allow read and write access
INLINE_CODE_68 Allow only read access

Networking

__TABLE_153_

Container Execution

Command Description
INLINE_CODE_79 Execute command in running container
INLINE_CODE_80 Execute with specific charset
INLINE_CODE_81 Get standard output from execution
INLINE_CODE_82 Get standard error from execution
INLINE_CODE_83 Get command exit code
INLINE_CODE_84 Copy file from container
INLINE_CODE_85 Stream container logs
INLINE_CODE_86 Log output to SLF4J logger
INLINE_CODE_87 Capture output as string
INLINE_CODE_88 Wait for specific output

JUnit 5 Integration

Command Description
INLINE_CODE_89 Enable TestContainers extension
INLINE_CODE_90 Mark field as container to manage
INLINE_CODE_91 Share container across all tests
INLINE_CODE_92 Inject container properties
INLINE_CODE_93 Add dynamic property
INLINE_CODE_94 Setup containers before all tests
INLINE_CODE_95 Cleanup containers after all tests
INLINE_CODE_96 Group tests with shared containers
INLINE_CODE_97 Start multiple containers in parallel
INLINE_CODE_98 Reuse container across test runs

Configuración

TestContainers Properties

Crear testcontainers.properties_ en 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());

Configuración de contenedores personalizados

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 Microservicios Prueba

@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 Prueba

@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());
    }
}

Buenas prácticas

Container Management

  • Use contenedores estáticos para clases de prueba: Compartir contenedores en todos los métodos de prueba para reducir el tiempo de inicio y el uso de recursos
  • Reutilización del contenedor de implementación: Habilitar testcontainers.reuse.enable=true para ciclos de prueba de desarrollo local más rápidos
  • Clean up resources: Utilizar @AfterAll o tratar con recursos cuando se administran manualmente contenedores fuera de la integración de JUnit
  • Utilice etiquetas de imagen específicas: Evite latest_ tag; utilice versiones específicas como postgres:15-alpine para pruebas reproducibles

Performance Optimization

  • Empiezo de contenedores paralelos: Utilice Startables.deepStart() para iniciar múltiples contenedores independientes simultáneamente
  • Minimize container restarts: Utilice el nivel de clase @Container en lugar de nivel de método cuando sea posible
  • Elija imágenes livianas: Preferir imágenes basadas en Alpine (por ejemplo, postgres:15-alpine_) para reducir el tiempo y el uso del disco
  • La implementación espera estrategias sabiamente: Utilizar estrategias de espera apropiadas para evitar retrasos innecesarios y asegurar la preparación de contenedores

Testing Strategy

  • Prueba contra las dependencias de producción* Utilice motores de base de datos reales y corredores de mensajes en lugar de versiones incrustadas ** Datos de prueba de aislamiento: Asegurar que cada prueba crea y limpia sus propios datos para prevenir las interdependencias de las pruebas
  • Usar alias de red: Crear redes personalizadas y utilizar alias significativos para la comunicación entre contenedores
  • Implement health checks: Siempre configurar estrategias de espera adecuadas para asegurar que los contenedores estén completamente listos antes de ejecutar las pruebas

CI/CD Integration

  • Verify Docker availability: Asegurar que el entorno CI tiene Docker instalado y accesible
  • Configure timeouts appropriately: Establecer fechas de inicio realistas considerando el rendimiento del entorno CI
  • Use el caché de la imagen: Configure las imágenes de Docker para acelerar las carreras posteriores
  • ** Utilización de los recursos de monitor**: Tenga en cuenta los límites de memoria y CPU en entornos CI; ajuste las configuraciones de contenedores en consecuencia

Seguridad y mantenimiento

  • Keep TestContainers actualizados: Actualización regular a las últimas versiones para parches de seguridad y nuevas características Utilizar imágenes oficiales: Preferir imágenes oficiales de Docker de editores verificados Evitar el modo privilegiado Sólo uso .withPrivilegedMode()_ cuando sea absolutamente necesario
  • Revisar los puertos expuestos: Sólo exponga los puertos que son realmente necesarios para la prueba

Troubleshooting

Issue Solution
Container fails to start: "Cannot connect to Docker daemon" Ensure Docker is running: INLINE_CODE_109. On Linux, add user to docker group: INLINE_CODE_110
Tests timeout during container startup Increase timeout: INLINE_CODE_111 or check Docker resources (CPU/Memory)
Port binding conflicts: "Address already in use" Use dynamic ports: INLINE_CODE_112 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 INLINE_CODE_113
Container starts but tests fail to connect Check wait strategy is appropriate: add INLINE_CODE_114 or INLINE_CODE_115
Out of memory errors during tests Reduce number of parallel containers, increase Docker memory limit in Docker Desktop settings, or use INLINE_CODE_116
"Ryuk container not found" warnings Disable Ryuk if needed: set INLINE_CODE_117 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 INLINE_CODE_118 and verify JDBC URL with INLINE_CODE_119
Container logs show errors but tests pass Enable log following: INLINE_CODE_120 to debug container issues
Slow test execution Use container reuse, share containers at class level, start containers in parallel with INLINE_CODE_121
"No such file or directory" when mounting volumes Use absolute paths or INLINE_CODE_122 for classpath resources
Network communication between containers fails Create custom network: INLINE_CODE_123 and attach containers with INLINE_CODE_124 and INLINE_CODE_125
TestContainers properties not being read Ensure INLINE_CODE_126 is in INLINE_CODE_127 and properly formatted
Container cleanup not happening Check Ryuk container is running: INLINE_CODE_128. Ensure tests complete normally without hanging

Módulos disponibles

Module Maven Artifact Description
PostgreSQL INLINE_CODE_129 PostgreSQL database containers
MySQL INLINE_CODE_130 MySQL database containers
MongoDB INLINE_CODE_131 MongoDB document database
Kafka INLINE_CODE_132 Apache Kafka message broker
Redis INLINE_CODE_133 Redis in-memory data store
Elasticsearch INLINE_CODE_134 Elasticsearch search engine
Cassandra INLINE_CODE_135 Cassandra NoSQL database
RabbitMQ INLINE_CODE_136 RabbitMQ message broker
Selenium INLINE_CODE_137 Browser automation containers
LocalStack INLINE_CODE_138 AWS services emulation