Vai al contenuto

TestContainers # Cheatsheet

Installazione

Prerequisiti_TABLE_146___

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>

Dipendenze di livello

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

Tabella_147_

Comandi di base

Container Lifecycle Operations

Tabella_148_

Generic Container Creation

Tabella_149_

Database Containers

TABELLA

Uso avanzato

Wait Strategies

Tabella_151_

File and Volume Operations

Tabella_152_

Networking

Tabella_153_

Esecuzione del contenitore

Tabella_154_

JUnit 5 Integrazione

Tabella_155_

Configurazione

TestContainers Properties

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

Configurazione del contenitore personalizzata

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

Migliori Pratiche

Gestione del contenitore

  • ** Utilizzare contenitori statici per classi di test**: Condividere contenitori attraverso tutti i metodi di test per ridurre il tempo di avvio e l'utilizzo delle risorse
  • **Riutilizzo container **: Abilita testcontainers.reuse.enable=true per cicli di test di sviluppo locali più veloci
  • **Creare risorse ** Utilizzare @AfterAll o provare-con-risorse quando si gestisce manualmente i contenitori al di fuori dell'integrazione JUnit
  • Utilizzare tag di immagine specifici: Evitare latest_ tag; utilizzare versioni specifiche come postgres:15-alpine per prove riproducibili

Ottimizzazione delle prestazioni

  • l'avvio dei container parallel. Usa Startables.deepStart() per avviare simultaneamente più contenitori indipendenti
  • Minimize container restarts**: Utilizzare il livello di classe @Container invece di livello di metodo quando possibile
  • Scegli immagini leggere: Preferisci immagini a base alpina (ad esempio postgres:15-alpine) per ridurre il tempo di estrazione e l'utilizzo del disco
  • **Aspetta le strategie con saggezza ** Utilizzare strategie di attesa appropriate per evitare ritardi inutili, assicurando la disponibilità dei container

Testing Strategy

  • **Test contro le dipendenze della produzione ** Utilizzare veri motori di database e broker di messaggi invece di versioni incorporate
  • ** Dati di prova Isolate**: Assicurarsi che ogni test crei e determini i propri dati per prevenire interdipendenze di prova
  • Utilizzare gli alias di rete: Creare reti personalizzate e utilizzare alias significativi per la comunicazione intercontainer
  • Implementare i controlli sanitari Configurare sempre strategie di attesa adeguate per garantire che i contenitori siano completamente pronti prima dell'esecuzione dei test

CI/CD Integrazione

  • Verify Docker disponibilità: Assicurare l'ambiente CI ha Docker installato e accessibile
  • Configurare i timeout in modo appropriato: Impostare tempi di avvio realistici considerando le prestazioni dell'ambiente CI
  • Utilizzare la cache dell'immagine: Configurare CI nella cache Immagini Docker per velocizzare le operazioni successive
  • ** Uso delle risorse motorie** Essere consapevoli dei limiti di memoria e CPU negli ambienti CI; regolare le configurazioni dei container di conseguenza

Sicurezza e manutenzione

  • Keep TestContainers aggiornato: Aggiornamento regolare alle ultime versioni per patch di sicurezza e nuove funzionalità
  • Utilizza immagini ufficiali: Preferisci le immagini ufficiali Docker da editori verificati
  • Avoid modalità privilegiata: Utilizzare solo .withPrivilegedMode() quando assolutamente necessario
  • **Recensione porte esposte ** Solo esporre le porte che sono effettivamente necessari per il test

Risoluzione dei problemi

Tabella_156

Moduli disponibili

TABLE_157| Nginx