Testcontainers — Cheat Sheet

Tag: testcontainers, docker, java, testing, integration-testing, junit, spring-boot

Livello: intermedio

TL;DR: Testcontainers è una libreria per creare istanze usa-e-getta di database e broker tramite Docker, garantendo test di integrazione su servizi reali e non su mock o database in-memory.

Concetti chiave

Avvia container Docker temporanei direttamente dal codice di test.

Sostituisce i database in-memory (come H2) con le versioni esatte usate in produzione.

Mappa le porte del container su porte casuali dell'host per evitare conflitti in ambienti di test paralleli.

Gestisce autonomamente il ciclo di vita del container, spegnendolo al termine dei test tramite il modulo Ryuk.

Fornisce moduli specifici (PostgreSQL, Kafka, LocalStack, MongoDB) e un GenericContainer per immagini custom.

Necessita di un demone Docker attivo (locale o nell'ambiente CI) per completare l'esecuzione.

Si integra nativamente con JUnit 5 tramite annotazioni dedicate e con Spring Boot per il binding dinamico delle properties.

Domande tipiche e risposte pronte

Q: Perché preferire Testcontainers a un database in-memory come H2 per i test di integrazione?R: H2 non replica perfettamente il dialetto o le funzioni avanzate del DB di produzione (es. jsonb su Postgres). Con Testcontainers avvio un container reale di Postgres, assicurandomi che migrazioni e query native funzionino esattamente come in ambiente reale.

Q: Come risolvi il problema delle porte dinamiche che cambiano a ogni avvio del container?R: In Spring Boot utilizzo l'annotazione @DynamicPropertySource per iniettare a runtime l'URL JDBC, comprensivo di porta assegnata, recuperandolo tramite i metodi getter dell'oggetto container.

Insidie e errori comuni

Scrivere porte statiche (es. 5432 per Postgres) nel file application-test.yml invece di usare il binding dinamico delle porte, causando il fallimento dei test.

Avviare e distruggere i container per ogni singola classe di test, aumentando drasticamente e inutilmente i tempi complessivi della build.

Dimenticare di abilitare i servizi Docker (es. Docker-in-Docker) nella pipeline CI/CD, facendo fallire i test in remoto.

Snippet e comandi

@Testcontainers
@SpringBootTest
class PostgresIntegrationTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")
        .withDatabaseName("testdb")
        .withUsername("testuser")
        .withPassword("testpass");

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

    @Test
    void testContainerIsRunning() {
        assertTrue(postgres.isRunning());
    }
}

Da ripassare

[ ] Pattern Singleton per riutilizzare una singola istanza del container tra tutte le classi di test della suite.

[ ] Esecuzione ottimizzata di Testcontainers all'interno di pipeline GitHub Actions e GitLab CI.

Riferimenti

Testcontainers Java Documentation

Spring Boot Integration Testing Guide

Jarvis Interviews
Jarvis