Pular para o conteúdo

Folha de Dicas do TestContainers

Folha de Dicas do TestContainers

Instalação

Pré-requisitos

RequisitoVersãoComando de Verificação
DockerÚltima estáveldocker --version
Java JDK8 ou superiorjava -version
Maven/GradleMais Recentemvn -v or gradle -v

Dependências Maven

<!-- 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>

Dependências Gradle

// 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'

Configuração do Docker por Plataforma

PlataformaComando de Instalação
Ubuntu/Debiancurl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
macOSbrew install --cask docker
WindowsBaixe o Docker Desktop em docker.com
Linux Post-Installsudo usermod -aG docker $USER && newgrp docker

Comandos Básicos

Operações de Ciclo de Vida do Contêiner

ComandoDescrição
container.start()Iniciar uma instância de container
container.stop()Parar e remover o container
container.isRunning()Verifique se o container está atualmente em execução
container.getContainerId()Obter ID do container Docker
container.getDockerImageName()Obter o nome da imagem utilizada
container.getLogs()Recuperar todos os logs de contêiner como string
container.getHost()Obter endereço do host (geralmente localhost)
container.getMappedPort(int)Obter porta do host mapeada para porta do container
container.getExposedPorts()Liste todas as portas de container expostas
container.getContainerInfo()Obtenha informações detalhadas do container

Criação de Contêiner Genérico

ComandoDescrição
new GenericContainer("image:tag")Criar contêiner a partir de qualquer imagem Docker
.withExposedPorts(port1, port2)Expor portas de contêiner para o host
.withEnv("KEY", "value")Definir variável de ambiente
.withCommand("cmd", "arg1")Substituir comando de contêiner
.withLabel("key", "value")Adicionar rótulo Docker ao container
.withNetworkMode("host")Definir modo de rede de contêiner
.withPrivilegedMode()Executar container em modo privilegiado
.withCreateContainerCmdModifier(cmd)Modificar comando de criação de container
.withStartupTimeout(Duration)Definir tempo máximo de espera na inicialização
.withReuse(true)Habilitar reutilização de contêineres entre testes

Contêineres de Banco de Dados

ComandoDescrição
new PostgreSQLContainer("postgres:15")Criar container PostgreSQL
new MySQLContainer("mysql:8.0")Criar container MySQL
new MongoDBContainer("mongo:6.0")Criar contêiner MongoDB
.withDatabaseName("dbname")Definir nome do banco de dados
.withUsername("user")Definir nome de usuário do banco de dados
.withPassword("pass")Definir senha do banco de dados
.getJdbcUrl()Obter URL de conexão JDBC
.getReplicaSetUrl()Obter URL do conjunto de réplicas do MongoDB (MongoDB)
.withInitScript("init.sql")Executar script SQL na inicialização
.withConfigurationOverride("path")Substituir configuração de banco de dados

Uso Avançado

Estratégias de Espera

ComandoDescrição
.waitingFor(Wait.forListeningPort())Aguarde até que a porta esteja escutando
.waitingFor(Wait.forHttp("/health"))Aguardar resposta do endpoint HTTP
.waitingFor(Wait.forLogMessage(".*ready.*", 1))Aguarde a mensagem de log específica
.waitingFor(Wait.forHealthcheck())Aguarde o Docker healthcheck ser aprovado
.waitingFor(Wait.forSuccessfulCommand("cmd"))Aguardar o comando ser bem-sucedido
.forStatusCode(200)Especificar código de status HTTP esperado
.forStatusCodeMatching(code -> code < 500)Correspondente de código de status personalizado
.withStartupTimeout(Duration.ofMinutes(2))Definir duração do tempo limite de inicialização
.withReadTimeout(Duration.ofSeconds(10))Definir tempo limite de leitura HTTP
.forResponsePredicate(response -> true)Validação personalizada de resposta

Operações de Arquivo e Volume

ComandoDescrição
.withFileSystemBind("host", "container")Montar diretório do host como bind mount
.withFileSystemBind(path, target, BindMode.READ_ONLY)Monte com modo específico
.withClasspathResourceMapping("res", "path")Montar recurso de classpath
.copyFileToContainer(MountableFile, "path")Copiar arquivo para o container
.withCopyFileToContainer(file, path)Copiar arquivo durante a inicialização
.withTmpFs(Map.of("/tmp", "rw"))Monte sistema de arquivos tmpfs
MountableFile.forClasspathResource("file")Arquivo de classpath de referência
MountableFile.forHostPath("/path")Caminho do sistema de arquivos do host de referência
BindMode.READ_WRITEPermitir acesso de leitura e gravação
BindMode.READ_ONLYPermitir apenas acesso de leitura

Networking

ComandoDescrição
Network.newNetwork()Criar rede Docker personalizada
.withNetwork(network)Anexar contêiner à rede
.withNetworkAliases("alias")Definir alias de rede para container
.dependsOn(otherContainer)Definir dependência de contêiner
.withExtraHost("hostname", "ip")Adicionar entrada ao /etc/hosts
.getNetworkAliases()Obter todos os aliases de rede
.withAccessToHost(true)Permitir que o container acesse o host
Network.SHAREDUsar rede compartilhada entre testes
.withNetworkMode("bridge")Definir modo de rede específico
.getNetwork()Obter contêiner de rede ao qual está anexado

Execução de Contêiner

ComandoDescrição
.execInContainer("cmd", "arg1")Executar comando no container em execução
.execInContainer(Charset, "cmd")Executar com charset específico
result.getStdout()Obter saída padrão da execução
result.getStderr()Obter erro padrão da execução
result.getExitCode()Obter código de saída do comando
.copyFileFromContainer("path", consumer)Copiar arquivo do container
.followOutput(consumer)Registros de logs de contêiner stream
new Slf4jLogConsumer(logger)Registrar saída de log no logger SLF4J
new ToStringConsumer()Capturar saída como string
new WaitingConsumer()Aguardar saída específica

Integração com JUnit 5

ComandoDescrição
@TestcontainersHabilitar extensão TestContainers
@ContainerMarcar campo como contêiner para gerenciar
static @ContainerCompartilhar container entre todos os testes
@DynamicPropertySourceInjetar propriedades do container
registry.add("prop", container::getUrl)Adicionar propriedade dinâmica
@BeforeAllConfigurar contêineres antes de todos os testes
@AfterAllLimpar containers após todos os testes
@NestedTestes de grupo com contêineres compartilhados
Startables.deepStart(containers).join()Iniciar vários containers em paralelo
.withReuse(true)Reutilizar container entre execuções de teste

Configuração

Propriedades do TestContainers

Criar testcontainers.propertiesem 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

Integração com Docker Compose

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

Configuração Personalizada de Contêiner

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");
    }
}

Integração com Spring Boot

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

Casos de Uso Comuns

Caso de Uso 1: Teste de Integração com PostgreSQL

Would you like me to continue with the remaining translations?```java @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());
}

}

```java
@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));
    }
}
```### Caso de Uso 3: Teste de Microsserviços Multi-Container
```java
@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());
    }
}
```### Caso de Uso 4: Teste de Integração com MongoDB
```java
@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")));
    }
}
```### Caso de Uso 5: Ambiente de Teste com Docker Compose
```java
@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());
    }
}
```## Melhores Práticas

### Gerenciamento de Containers
- **Use containers estáticos para classes de teste**: Compartilhe containers entre todos os métodos de teste para reduzir tempo de inicialização e uso de recursos
- **Implemente reutilização de containers**: Habilite`testcontainers.reuse.enable=true`para ciclos de desenvolvimento local de testes mais rápidos
- **Limpe recursos**: Use`@AfterAll`ou try-with-resources ao gerenciar containers manualmente fora da integração JUnit
- **Use tags de imagem específicas**: Evite tag`latest`; use versões específicas como`postgres:15-alpine`para testes reproduzíveis

### Otimização de Desempenho
- **Inicialização paralela de containers**: Use`Startables.deepStart()`para iniciar múltiplos containers independentes simultaneamente
- **Minimize reinícios de containers**: Use`@Container`em nível de classe em vez de nível de método quando possível
- **Escolha imagens leves**: Prefira imagens baseadas em Alpine (por exemplo,`postgres:15-alpine`) para reduzir tempo de download e uso de disco
- **Implemente estratégias de espera com sabedoria**: Use estratégias de espera apropriadas para evitar atrasos desnecessários, garantindo a prontidão do container

### Estratégia de Teste
- **Teste com dependências semelhantes à produção**: Use mecanismos de banco de dados e message brokers reais em vez de versões embutidas
- **Isole dados de teste**: Garanta que cada teste crie e limpe seus próprios dados para evitar interdependências
- **Use aliases de rede**: Crie redes personalizadas e use aliases significativos para comunicação entre containers
- **Implemente verificações de saúde**: Sempre configure estratégias de espera adequadas para garantir que os containers estejam totalmente prontos antes da execução dos testes

### Integração CI/CD
- **Verifique a disponibilidade do Docker**: Certifique-se de que o ambiente de CI tenha Docker instalado e acessível
- **Configure timeouts adequadamente**: Defina timeouts de inicialização realistas considerando o desempenho do ambiente de CI
- **Use cache de imagens**: Configure o CI para armazenar imagens Docker para acelerar execuções subsequentes
- **Monitore uso de recursos**: Esteja ciente dos limites de memória e CPU em ambientes de CI; ajuste configurações de containers adequadamente

### Segurança e Manutenção
- **Mantenha TestContainers atualizado**: Atualize regularmente para as versões mais recentes para correções de segurança e novos recursos
- **Use imagens oficiais**: Prefira imagens Docker oficiais de editores verificados
- **Evite modo privilegiado**: Use`.withPrivilegedMode()`apenas quando absolutamente necessário
- **Revise portas expostas**: Exponha apenas portas realmente necessárias para teste

## Solução de Problemas

| Problema | Solução |
|-------|----------|
| **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** | Certifique-se de que o CI tenha o Docker instalado, verifique as configurações de timeout, confirme a conectividade de rede, revise os limites de recursos do ambiente CI |
| **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 | grep ryuk`. Ensure tests complete normally without hanging |
## Módulos Disponíveis

| Módulo | Artefato Maven | Descrição |
|--------|---------------|-------------|
| PostgreSQL | `testcontainers-postgresql` | Contêineres de banco de dados PostgreSQL |
| MySQL | `testcontainers-mysql` | Contêineres de banco de dados MySQL |
| MongoDB | `testcontainers-mongodb` | Banco de dados de documentos MongoDB |
| Kafka | `testcontainers-kafka` | Corretor de mensagens Apache Kafka |
| Redis | `testcontainers-redis` | Armazenamento de dados em memória Redis |
| Elasticsearch | `testcontainers-elasticsearch` | Motor de busca Elasticsearch |
| Cassandra | `testcontainers-cassandra` | Banco de dados NoSQL Cassandra |
| RabbitMQ | `testcontainers-rabbitmq` | Corretor de mensagens RabbitMQ |
| Selenium | `testcontainers-selenium` | Contêineres de automação de navegador |
| LocalStack | `testcontainers-localstack` | Emulação de serviços AWS || Nginx