Salta al contenuto principale

Testcontainers nei Test di Integrazione di Spring Boot

Spring Testcontainers Testing
Autore
Harpal Singh
Software Engineer
Tradotto da
Namastecode
Indice dei contenuti

1. Introduzione

In questo tutorial, esploreremo come utilizzare testcontainers in un’applicazione Spring Boot durante la creazione di test di integrazione.

I Testcontainers sono istanze leggere e usa e getta di servizi infrastrutturali come database, broker di messaggi, ecc., che possono essere eseguite in container Docker. Quando scriviamo test di integrazione, ci piacerebbe avere questi servizi disponibili invece di simularli o gestire la configurazione e la pulizia utilizzando script personalizzati soggetti a errori.

Vediamo come possiamo configurare un contenitore di database utilizzando testcontainers.

2. Prerequisiti

Innanzitutto, dobbiamo configurare il nostro progetto per lavorare con testcontainers. Possiamo scaricare e installare Docker dal sito ufficiale: https://www.docker.com/get-started

Successivamente, dobbiamo aggiungere le dipendenze di testcontainers nel nostro file pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-testcontainers</artifactId>
</dependency>
<dependency
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>1.19.7</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.19.7</version>
    <scope>test</scope>
</dependency>

Assicurati di ottenere l’ultima versione delle dipendenze junit-jupiter e testcontainers-postgresql. Inoltre, assicurati di utilizzare la versione corretta per spring-boot-testcontainers, poiché alcune classi potrebbero non essere disponibili nella versione precedente di Spring Boot.

3. Utilizzare Testcontainers nei test di Spring Boot

Spring Boot ha incluso il supporto per Testcontainers da un po’ di tempo. Tuttavia, con il rilascio di Spring Boot 3.1, l’integrazione è stata ulteriormente migliorata. Vediamo come il vecchio approccio si confronta con il nuovo.

Nelle versioni precedenti a Spring Boot 3.1, dovevamo configurare manualmente Spring Boot per connetterci ai servizi in esecuzione all’interno dei contenitori. Questo spesso comportava l’inclusione di un po’ di codice boilerplate che coinvolgeva l’uso dell’annotazione @DynamicPropertySource per impostare le proprietà necessarie:

@SpringBootTest
@Testcontainers
class MyIntegrationTests {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");

    @Test
    void myTest() {
        // ...
    }

    @DynamicPropertySource
    static void postgresProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }
}

Sopra, abbiamo annotato il test con @Testcontainer per far sapere alla classe di test che utilizza testcontainers. Abbiamo poi definito un’istanza del container PostgreSQL utilizzando l’annotazione @Container. Questo avvierà un container PostgreSQL con la versione specificata. Ora, di solito avremmo configurato le proprietà di connessione all’interno del file application.properties. Ma poiché stiamo usando testcontainers, non sappiamo ancora quali saranno le proprietà di connessione. Quindi utilizziamo @DynamicPropertySource per recuperare le proprietà di connessione dall’istanza del container e impostarle nel contesto dell’applicazione Spring Boot.

Come possiamo vedere, questo approccio coinvolge alcuni elementi in movimento ed è molto verboso anche per un semplice test.

A partire da Spring Boot 3.1, possiamo utilizzare l’annotazione @ServiceConnection per semplificare il processo:

@SpringBootTest
@Testcontainers
class MyIntegrationTests {

    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");

    @Test
    void myTest() {
        // ...
    }
}

Qui, abbiamo sostituito l’annotazione @DynamicPropertySource con @ServiceConnection. Questo elimina la necessità di ricordare tutte le proprietà per un determinato servizio e consente a Spring Boot di gestire automaticamente la configurazione della connessione. In dettaglio, funziona scoprendo il tipo di contenitore e creando un corrispondente bean ConnectionDetails. Poi, Spring Boot utilizza questo per configurare la connessione al servizio in esecuzione nel Testcontainer.

Possiamo controllare tutti i contenitori supportati nella documentazione ufficiale di Spring Boot.

4. Utilizzo di Testcontainers Durante lo Sviluppo

Facciamo un passo avanti per vedere come possiamo utilizzare Testcontainers durante lo sviluppo, sfruttando le nuove funzionalità in Spring Boot 3.1.

Storicamente, abbiamo utilizzato profili diversi per passare tra gli ambienti di sviluppo e di test. Ma ora, c’è un modo migliore per gestire questa situazione e rendere l’ambiente di sviluppo il più simile possibile all’ambiente di produzione.

Invece di creare un profilo di test, possiamo creare un nuovo metodo main per eseguire la nostra applicazione localmente utilizzando testcontainers:

public class TestMyApplication {
    public static void main(String[] args) {
        SpringApplication.from(MyApplication::main).run(args);
    }
}

Dobbiamo creare questa classe all’interno del pacchetto radice dei test per mantenere le dipendenze di testcontainers solo nell’ambito dei test. Altrimenti, le dipendenze vengono incluse nei file jar completi, il che aumenta le dimensioni dell’applicazione e non viene utilizzato in produzione.

Ora, dobbiamo configurare il contenitore di test per PostgreSQL e far sapere al nuovo metodo main di esso. Prima di tutto, creiamo un file di configurazione e annotiamolo con @TestConfiguration:

@TestConfiguration(proxyBeanMethods = false)
class MyContainersConfiguration {
    @Bean
    @ServiceConnection
    PostgreSQLContainer<?> postgresContainer() {
        return new PostgreSQLContainer<>("postgres:16");
    }
}

In breve, stiamo semplicemente creando un bean che rappresenta l’istanza del contenitore di PostgreSQL. Ancora una volta, lo annotiamo con @ServiceConnection per far sapere a Spring Boot che dovrebbe gestire il ciclo di vita e configurarlo per l’uso con la nostra applicazione.

Ora, aggiorniamo il metodo test-main:

public class TestMyApplication {
    public static void main(String[] args) {
        SpringApplication.from(MyApplication::main)
            .with(MyContainersConfiguration.class)
            .run(args);
    }
}

Infine, possiamo eseguire il metodo test-main per avviare l’applicazione utilizzando Maven:

./mvnw spring-boot:test-run

Oppure usando Gradle:

./gradlew bootTestRun

Questo avvierà l’applicazione e i contenitori si avvieranno e si fermeranno automaticamente insieme all’applicazione. Come possiamo vedere, i plugin di maven e gradle già conoscono il metodo test-main e avvieranno l’applicazione utilizzando quello.

5. Salvataggio dei Dati del Container Tra i Riavvii

Con la configurazione sopra, stiamo creando una nuova istanza del container ogni volta che l’applicazione viene avviata. Pertanto, perdiamo tutti i loro dati. Tuttavia, potremmo voler salvare lo stato del container tra i riavvii. Per farlo, possiamo utilizzare l’annotazione @RestartScope di Spring Boot DevTools sui metodi del bean del container:

@Bean
@ServiceConnection
@RestartScope
PostgreSQLContainer<?> postgresContainer() {
    return new PostgreSQLContainer<>("postgres:16");
}

In alternativa, possiamo utilizzare la funzionalità sperimentale dei contenitori riutilizzabili in Testcontainers chiamando withReuse(true) sul contenitore:

new PostgreSQLContainer<>("postgres:16").withReuse(true);

Questo salverà i dati tra i riavvii. Inoltre, questo accelera il tempo di avvio dell’applicazione poiché non creiamo un nuovo container ogni volta.

6. Conclusione

In questo articolo, abbiamo visto come utilizzare Testcontainers nei test di integrazione di Spring Boot. Abbiamo esaminato come impostare un container di database utilizzando Testcontainers e come utilizzare l’annotazione @ServiceConnection per semplificare il processo. Abbiamo anche visto come utilizzare Testcontainers durante lo sviluppo sfruttando le nuove funzionalità in Spring Boot 3.1.

Related

Restituire errori HTTP 4XX in un'applicazione Spring
Spring HTTP
Ottieni Valori Definiti nel File di Proprietà in Spring
Spring Properties Basics
Configurare la Porta per un'Applicazione Spring Boot
Spring Basics