Maximize Page
Tech & DevOps HubEspace Tech & DevOps: Explorez le monde du Dev, du Cloud et des outils DevOps à travers nos articles et discussions Explore the world of development, the cloud and DevOps tools

Ajout des tests d'intégration

Date de l'article:02-11-2025
CI/CD Spring Boot
Ajout de tests d'intégration Spring Boot avec base PostgreSQL réelle via service container GitHub Actions, et H2 en local. On couvre la structure des tests, les composants clés et le choix entre service container et Testcontainers

Cet article a pour objectif d'ajouter et de configurer le plugin "Failsafe", d'illustrer son utilisation à travers des extraits de tests d'intégration (IT), accompagnés de recommandations selon le contexte, ainsi que d'intégrer ces étapes dans un pipeline CI GitHub Actions.


Définition et position des tests d'intégrations dans la pyramide
Analyse des niveaux

Tests Unitaires (Base): Ils forment la fondation car ils sont rapides et peu coûteux. Ils valident la logique isolée de chaque composant.

Tests d'Intégration (Milieu): Ils vérifient comment les composants interagissent et communiquent entre eux.
Ils sont plus lents que les tests unitaires mais indispensables pour détecter les erreurs d'interface.

Tests E2E / UI (Sommet): Ils testent le système complet du point de vue de l'utilisateur.
Bien qu'essentiels, ils sont maintenus en faible nombre car ils sont fragiles, lents et coûteux à exécuter.

Les tests d'intégration valident les interactions entre plusieurs couches de l'application: "Controller → Service → Repository → base de données"

Contrairement aux tests unitaires qui isolent le code en simulant (mock) les dépendances,
les tests d'intégration vérifient la communication et la compatibilité entre les différentes parties du code et les systèmes externes, comme une base de données réelle.

Nous utilisons ici une vraie base de données: PostgreSQL en CI, H2 ou Postgres en local selon la configuration définie

En résumé:
  • Tests unitaires: Rapides, isolés, sans I/O réseau ni vraie BDD
  • Tests d'intégration: Chargent le contexte Spring Boot complet, appellent de “vraies” routes HTTP (MockMvc ou RestTemplate)
         et exécutent des requêtes SQL réelles
  • Pyramide de tests: La majorité des tests restent unitaires (70-80%), les tests d'intégration représentent typiquement 10-20% du total,
         les E2E une petite couche au sommet
  • Objectif de temps: faire tourner les tests d'intégration en moins de ~5 minutes sur la CI pour qu'ils restent utiles au quotidien (23sec en staging)

Étapes de construction et composants clés:
Setup du pom.xml (après surefire):
<plugin>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>3.5.3</version>
    <executions>
        <execution>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Ajout & Configuration du plugin Maven "Failsafe"

Les tests d'intégration Spring Boot se construisent en chargeant tout ou partie du contexte de l'application, généralement via @SpringBootTest,
pour vérifier l'interaction entre les composants (Contrôleurs, Services, Repositories)

Les tests d'intégration sont exécutés par le plugin "Failsafe",
     distinct de "Surefire" qui gère les tests unitaires

Convention de nommage: Failsafe exécute par défaut les classes dont le nom se termine par IT, ITCase, etc., d'où le nom FlashcardIntegrationIT


Structure des tests et extraits
Comme pour les tests unitaires, ils sont localisés dans "src/test/java/../flashcards/", dossier "integration"
Annotations principales:
  • @SpringBootTest: Lance le contexte Spring complet
  • @AutoConfigureMockMvc: Configure automatiquement MockMvc pour simuler des requêtes HTTP sans lancer un serveur complet
  • @Transactional: Souvent ajouté pour annuler les modifications en base de données après chaque test, gardant ainsi un environnement propre
Structuration des tests (les 3A):
  • Arrange (Préparer): Initialise les données, configure les mocks (ex: Mockito.when)
  • Act (Agir): Exécute l'action, par exemple via mockMvc.perform (get("/api/flashcards"))
  • Assert (Vérifier): Vérifie le résultat avec andExpect(status().isOk()) ou assertThat

Retrouver le code complet des tests d'intégration dans le repository de l'application Flashcard
Exemple: FlashcardIntegrationIT (extrait)
package com.example.flashcards.integration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest
@ActiveProfiles("it")
@AutoConfigureMockMvc
class FlashcardIntegrationIT {

  @Autowired private MockMvc mockMvc;
  @Autowired private ObjectMapper objectMapper;
  @Test
  void testCRUDFLashcard() throws Exception {

    CategoryDto category = new CategoryDto(null, "Math");
    String categoryJson = objectMapper.writeValueAsString(category);

    String categoryResponse =
        mockMvc
            .perform(
                post("/api/categories")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(categoryJson))
            .andExpect(status().isOk())
            .andReturn()
            .getResponse()
            .getContentAsString();
    CategoryDto createdCategory = objectMapper.readValue(categoryResponse, CategoryDto.class);
  }
}

→ Le script crée une catégorie pour les Flashcards

MockMvc
permet de tester la couche Controller (Mapping, Sérialisation JSON, Validation) sans le coût réseau d'un vrai serveur HTTP, ce qui est le parfait compromis pour des IT rapides
@SpringBootTest + @service Container + @Container (PostgreSQL)
@Sql
pour seed data avant/après test
TestRestTemplate
ou MockMvc pour endpoints
Assert DB
état final (via repo ou queries)

au Piège
     Si vous oublier @ActiveProfiles("it")
      → Il pourrait utiliser H2 en production!
Résultat:

Transactions non rollback → sale état entre tests
Port random vs fixed en IT (@LocalServerPort)


Configuration du fichier "src/test/resources/application-it.properties"
# IT Behavior
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=false

# Migrations
spring.liquibase.enabled=false
spring.flyway.enabled=false

spring.sql.init.mode=never

Est en quelque sorte un fichier de profil, utilisé pour configurer la base de donnés, les ports etc. Et ce, au même titre que les fichiers situé dans src/main/resources/
Mais aussi et surtout utilisés par les classes de tests.

Ce fichier utilise un minimum de configuration, il sera configuré dans les workflows, tantôt pour H2 (local / dev), tantôt avec une réelle base de données Postgres (staging / main)


Intégration dans la CI GitHub Actions

Nous devons nous referer au profil "it" qui ne dispose pas de configuration spécifiques, nous devons donc lui passer les paramètres à utiliser pour la base de données

1. La branche develop
name: CI - Develop
jobs:
  build-and-test:
    runs-on: ubuntu-24.04
    env:
      SPRING_DATASOURCE_URL: jdbc:h2:mem:itdb;DB_CLOSE_DELAY=-1
      SPRING_DATASOURCE_DRIVER_CLASS_NAME: org.h2.Driver
      SPRING_DATASOURCE_USERNAME: sa
      SPRING_DATASOURCE_PASSWORD: 
    steps:
      - name: Run Maven clean verify
        run: ./mvnw -B clean verify jacoco:report  

Utilise H2 comme en local:
Les variables d'environements de la base de données utilise la même configuration que le profil "test", situé dans src/test/resources/application-test.properties

Le profil "dev" étant le profil par défaut, les tests étant annotés, il n'est pas nécessaire ici de préciser de profil actif

La commande verify utilisée dans le run, lance tous les tests en une fois (Spotbug, Checkstyle, Junit et tests d'intégration)

2. La branche staging
- name: Run Integration Tests
  env:
    SPRING_PROFILES_ACTIVE: it
    SPRING_DATASOURCE_URL: jdbc:postgresql://localhost:5432/flashcardsdb
    SPRING_DATASOURCE_USERNAME: postgres
    SPRING_DATASOURCE_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
  run: ./mvnw -B verify -DskipUnitTests=true

Utilise une base Postgres réelle

Utilise explicitement le profil "it"

Les tests sont lancés dans une étape séparée

3. La branche main
- name: Run Integration Tests (release only)
  if: env.IS_RELEASE == 'true'
  run: ./mvnw -B verify -DskipUnitTests

Comme en staging, les tests sont lancés dans une étape séparée et utilise une base réelle

Execution uniquement sur un push avec tag


Service Container ou testContainer?

Bien qu'il soit recommandé d'utiliser testContainer en 2026, j'ai fait le choix de ne pas l'utiliser, et ce, pour plusieurs raisons:

Pas de dépendance à Docker en local

Les développeurs peuvent lancer les tests d'intégration sans avoir Docker installé sur leur machine.

En local et sur la branche develop, les tests d'intégration utilisent H2 en mémoire, ce qui est rapide et simplifie énormément l'onboarding

PostgreSQL seulement en CI

Une vraie base PostgreSQL est lancée uniquement dans la CI,
via un service container GitHub Actions (staging/main)

Le pipeline se charge de démarrer et d'arrêter le container, ce qui garantit un environnement propre à chaque run

Dans l'article suivant, nous continuerons sur la préparation des tests, avec la création des scénarios de tests API, en local avec Postman

Laissez-moi un commentaire

En postant un commentaire anonyme, vous adhérez automatiquement aux conditions d'utilisation du site.