1. Introduction
Dans ce tutoriel, nous allons découvrir comment utiliser testcontainers dans une application Spring Boot lors de la création de tests d’intégration.
Les Testcontainers sont des instances légères et jetables de services d’infrastructure tels que des bases de données, des courtiers de messages, etc., qui peuvent s’exécuter dans des conteneurs Docker. Lorsque nous écrivons des tests d’intégration, nous aimerions avoir ces services disponibles au lieu de les simuler ou de gérer la configuration et le démontage en utilisant des scripts personnalisés sujets à des erreurs.
Voyons comment nous pouvons configurer un conteneur de base de données en utilisant testcontainers.
2. Prérequis
Tout d’abord, nous devons configurer notre projet pour travailler avec testcontainers. Nous pouvons télécharger et installer Docker depuis le site officiel : https://www.docker.com/get-started
Ensuite, nous devons ajouter les dépendances testcontainers dans notre fichier 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>
Assurez-vous d’obtenir la dernière version des dépendances testcontainers junit-jupiter et testcontainers-postgresql. De plus, veillez à utiliser la bonne version pour spring-boot-testcontainers, car certaines classes pourraient ne pas être disponibles dans les anciennes versions de Spring Boot.
3. Utiliser Testcontainers dans les tests Spring Boot
Spring Boot a inclus le support pour Testcontainers depuis un certain temps. Cependant, avec la sortie de Spring Boot 3.1, l’intégration a été encore améliorée. Voyons comment l’ancienne approche se compare à la nouvelle.
Dans les versions antérieures à Spring Boot 3.1, nous devions configurer manuellement Spring Boot pour nous connecter aux services fonctionnant à l’intérieur des conteneurs. Cela conduit souvent à inclure un certain code standard impliquant l’utilisation de l’annotation @DynamicPropertySource pour définir les propriétés nécessaires :
@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);
}
}
Au-dessus, nous avons annoté le test avec @Testcontainer pour informer la classe de test qu’elle utilise des testcontainers. Nous avons ensuite défini une instance du conteneur PostgreSQL en utilisant l’annotation @Container. Cela va démarrer un conteneur PostgreSQL avec la version spécifiée. Maintenant, d’habitude, nous aurions configuré les propriétés de connexion dans le fichier application.properties. Mais comme nous utilisons des testcontainers, nous ne savons pas encore quelles seront les propriétés de connexion. Nous utilisons donc @DynamicPropertySource pour récupérer les propriétés de connexion de l’instance du conteneur et les définir dans le contexte de l’application Spring Boot.
Comme nous pouvons le voir, cette approche implique quelques éléments en mouvement et est très verbeuse même pour un test simple.
À partir de Spring Boot 3.1, nous pouvons utiliser l’annotation @ServiceConnection pour simplifier le processus :
@SpringBootTest
@Testcontainers
class MyIntegrationTests {
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
@Test
void myTest() {
// ...
}
}
Ici, nous avons remplacé l’annotation @DynamicPropertySource par @ServiceConnection. Cela élimine le besoin de se souvenir de toutes les propriétés pour un service donné et permet à Spring Boot de gérer automatiquement la configuration de la connexion. En détail, cela fonctionne en découvrant le type de conteneur et en créant un bean ConnectionDetails correspondant. Ensuite, Spring Boot utilise cela pour configurer la connexion au service s’exécutant dans le Testcontainer.
Nous pouvons vérifier tous les conteneurs pris en charge dans la documentation officielle de Spring Boot.
4. Utiliser Testcontainers Pendant le Développement
Allons un peu plus loin pour voir comment nous pouvons utiliser Testcontainers pendant le développement en utilisant les nouvelles fonctionnalités de Spring Boot 3.1.
Historiquement, nous utilisions différents profils pour passer d’un environnement de développement à un environnement de test. Mais maintenant, il existe une meilleure façon de gérer cela et de rendre l’environnement de développement aussi similaire que possible à l’environnement de production.
Au lieu de créer un profil de test, nous pouvons créer une nouvelle méthode main pour exécuter notre application localement en utilisant testcontainers :
public class TestMyApplication {
public static void main(String[] args) {
SpringApplication.from(MyApplication::main).run(args);
}
}
Nous devons créer cette classe à l’intérieur du paquet racine des tests pour garder les dépendances testcontainers uniquement dans le scope de test. Sinon, les dépendances sont incluses dans les fichiers jar complets, ce qui gonfle la taille de l’application et n’est pas utilisé en production.
Maintenant, nous devons configurer le conteneur de test pour PostgreSQL et informer la nouvelle méthode principale à ce sujet. Tout d’abord, nous créons un fichier de configuration et l’annotons avec @TestConfiguration :
@TestConfiguration(proxyBeanMethods = false)
class MyContainersConfiguration {
@Bean
@ServiceConnection
PostgreSQLContainer<?> postgresContainer() {
return new PostgreSQLContainer<>("postgres:16");
}
}
En résumé, nous créons simplement un bean qui représente l’instance de conteneur de PostgreSQL. Encore une fois, nous l’annotons avec @ServiceConnection pour indiquer à Spring Boot qu’il doit gérer le cycle de vie ainsi que le configurer pour l’utiliser avec notre application.
Maintenant, nous mettons à jour la méthode test-main :
public class TestMyApplication {
public static void main(String[] args) {
SpringApplication.from(MyApplication::main)
.with(MyContainersConfiguration.class)
.run(args);
}
}
Enfin, nous pouvons exécuter la méthode test-main pour démarrer l’application en utilisant Maven :
./mvnw spring-boot:test-run
Ou en utilisant Gradle :
./gradlew bootTestRun
Cela démarrera l’application et les conteneurs démarreront et s’arrêteront automatiquement avec l’application. Comme nous pouvons le voir, les plugins maven et gradle connaissent déjà la méthode test-main et démarreront l’application en utilisant celle-ci.
5. Sauvegarder les données du conteneur entre les redémarrages
Avec la configuration ci-dessus, nous créons une nouvelle instance du conteneur chaque fois que l’application est démarrée. Par conséquent, nous perdons toutes leurs données. Cependant, nous pourrions vouloir sauvegarder l’état du conteneur entre les redémarrages. Pour ce faire, nous pouvons utiliser l’annotation @RestartScope de Spring Boot DevTools sur les méthodes des beans du conteneur :
@Bean
@ServiceConnection
@RestartScope
PostgreSQLContainer<?> postgresContainer() {
return new PostgreSQLContainer<>("postgres:16");
}
Alternativement, nous pouvons utiliser la fonctionnalité expérimentale des conteneurs réutilisables dans Testcontainers en appelant withReuse(true) sur le conteneur :
new PostgreSQLContainer<>("postgres:16").withReuse(true);
Cela sauvegardera les données entre les redémarrages. De plus, cela accélère le temps de démarrage de l’application car nous ne lançons pas un nouveau conteneur à chaque fois.
6. Conclusion
Dans cet article, nous avons vu comment utiliser Testcontainers dans les tests d’intégration de Spring Boot. Nous avons examiné comment configurer un conteneur de base de données en utilisant Testcontainers et comment utiliser l’annotation @ServiceConnection pour simplifier le processus. Nous avons également vu comment utiliser Testcontainers pendant le développement en tirant parti des nouvelles fonctionnalités de Spring Boot 3.1.