Spring Boot propose un bon niveau de support pour tester nos requêtes MongoDB via le module Embedded MongoDB Database. Mais il peut être intéressant d’avoir exactement la même version de base de données que celle de production.
Nous allons voir comment nous pouvons utiliser Testcontainers pour les tests d’intégration avec Spring Data MongoDB.
Testcontainers est une librairie Java qui s’appuie sur les containers Docker pour apporter une solution légère aux tests d’intégration des différentes bases de données, Selenium web browsers, etc.
Testcontainers facilite les types de tests suivants :
Vous devez avoir :
Nous allons utiliser Spring Initializr pour créer la base de notre projet avec toutes les dépendances.
Sur la partie gauche, j’ai sélectionné Gradle, Java, Spring Boot 2.7.3, packaging en Jar, Java 17. Vous pouvez choisir bien entendu d’autres options si vous êtes plus à l’aise avec Java 11 ou Maven.
Sur la partie droite, nous devons prendre les dépendances Spring Data MongoDB, Testcontainers. J’ai ajouté Lombok mais ce n’est pas obligatoire.
Il n’y a plus qu’à appuyer sur le bouton GENERATE.
Une fois le projet décompressé, vous devriez trouver un fichier gradle similaire à celui-ci :
plugins {id 'org.springframework.boot' version '2.7.3'id 'io.spring.dependency-management' version '1.0.13.RELEASE'id 'java'}group = 'com.aircodr'version = '0.0.1-SNAPSHOT'sourceCompatibility = '17'configurations {compileOnly {extendsFrom annotationProcessor}}repositories {mavenCentral()}ext {set('testcontainersVersion', "1.17.3")}dependencies {implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'compileOnly 'org.projectlombok:lombok'annotationProcessor 'org.projectlombok:lombok'testImplementation 'org.springframework.boot:spring-boot-starter-test'testImplementation 'org.testcontainers:junit-jupiter'testImplementation 'org.testcontainers:mongodb'}dependencyManagement {imports {mavenBom "org.testcontainers:testcontainers-bom:${testcontainersVersion}"}}tasks.named('test') {useJUnitPlatform()}
Les seules dépendances non liées à Spring Boot sont les deux de Testcontainers. La dépendance org.testcontainers:junit-jupiter
contient l’extension Testcontainers pour JUnit Jupiter que nous allons utiliser pour gérer le cycle de vie de notre container.
La dépendance org.testcontainers:mongodb
contient le module Testcontainers pour MongoDB.
Pour conserver les deux dépendances correctement alignées nous utilisons org.testcontainers:testcontainers-bom
(Bill of Materials) dans le bloc dependencyManagement
.
Pour avoir un peu de code métier à tester, nous allons créer une classe User.
@Data@Builder@Documentpublic class User {@Idprivate String id;private String name;private Integer rating;}
@Document
et @Id
sont des annotations Spring Data MongoDB. Elles sont semblables à @Entity
et @Id
en JPA.
Les annotations @Data
et @Builder
viennent de la librairie Lombok. Elles me permettent de ne pas avoir à générer les getters, setters, constructeurs, etc. Si vous n’avez pas ajouté Lombok vous devrez générer les méthodes manquantes.
Nous allons ensuite créer une interface UserRepository
qui va étendre MongoRepository
. Le fait d’étendre l’interface MongoRepository
permet d’ajouter tout un tas de méthodes comme .save()
, .delete()
, .findAll()
, etc. à notre interface UserRepository
.
Nous allons en profiter pour enrichir notre interface avec la méthode findAllByRatingBetween
qui permettra de récupérer une liste ordonnée de User
dont la note est comprise entre deux bornes.
public interface UserRepository extends MongoRepository<User, String> {@Query(sort = "{rating: 1}")List<User> findAllByRatingBetween(int min, int max);}
Même si nous l’avons ajouté, la méthode findAllByRatingBetween
s’appuie beaucoup sur le framework Spring Data MongoDB et a donc peu de chance de présenter un quelconque problème.
En contexte réel, il est bien plus intéressant de concentrer ses efforts sur des requêtes plus avancées comme celles contenant des SpEL Expressions.
Nous pouvons maintenant passer à l’écriture des tests pour l’interface CustomerRepository
.
La mise en place va suivre les étapes suivantes :
spring.data.mongodb.uri
pour la faire pointer vers la base de données car Testcontainers utilise un port aléatoire éphémère.@Testcontainers@DataMongoTest(excludeAutoConfiguration = EmbeddedMongoAutoConfiguration.class)class UserRepositoryTest {@Containerstatic MongoDBContainer mongoDBContainer = new MongoDBContainer("mongo:5.0.10");@DynamicPropertySourcestatic void setProperties(DynamicPropertyRegistry registry) {registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl);}}
@Testcontainers
s’occupe du cycle de vie du container.@DataMongoTest
va désactiver l’auto-configuration du projet et appliquer à la place seulement les configurations pour les tests MongoDB. Par défaut, les tests annotés avec @DataMongoTest
utilise une base de données MongoDB embarquée (si disponible). Nous excluons l’auto-configuration de la base de données embarquée via la propriété exclude de l’annotation @DataMongoTest
.@DynamicPropertySource
va permettre de modifier l’URI de la base de données MongoDB avant le démarrage du contexte.Une fois la configuration mise en place il ne reste plus qu’à écrire notre test.
@Testcontainers@DataMongoTest(excludeAutoConfiguration = EmbeddedMongoAutoConfiguration.class)class UserRepositoryTest {@Containerstatic MongoDBContainer mongoDBContainer = new MongoDBContainer("mongo:5.0.10");@DynamicPropertySourcestatic void setProperties(DynamicPropertyRegistry registry) {registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl);}@Autowiredprivate UserRepository userRepository;@AfterEachpublic void cleanUp() {userRepository.deleteAll();}@Testpublic void shouldReturnListOfUserWithMatchingRate() {userRepository.save(User.builder().name("John Doe").rating(1).build());userRepository.save(User.builder().name("William Smith").rating(2).build());userRepository.save(User.builder().name("Jane Valentine").rating(4).build());List<User> users = userRepository.findAllByRatingBetween(0, 3);assertEquals(2, users.size());assertEquals("John Doe", users.get(0).getName());assertEquals("William Smith", users.get(1).getName());}}
Il ne vous reste plus qu’à tester.
Pour aller plus loin, sachez qu’il est possible de configurer Testcontainers pour pouvoir réutiliser un container Docker déjà démarré ou en démarrer un nouveau pour chaque test.
Le code source de cet exemple est disponible sur Github.
Les technologies Spring sont au cœur de mon travail depuis plus de 10 ans.
N’hésitez pas à me contacter pour vos projets Spring Boot.