Nouveau : Datasets open source gratuits disponibles !Decouvrir →
🧪
Intermediaire 25 min Spring Boot

Tester Spring Boot avec JUnit

Pourquoi Tester Spring Boot avec JUnit ?

Dans un environnement de développement moderne, tester est une pratique cruciale qui permet non seulement d'assurer la qualité du code mais aussi de prévenir les bugs avant qu'ils ne puissent atteindre l'environnement de production. Avec Spring Boot, une plateforme populaire pour le développement de microservices et d'applications web, l'utilisation de JUnit pour le testing devient indispensable.

Un cas d'usage concret : Imaginez un système de gestion des utilisateurs qui nécessite des fonctionnalités telles que la création, la mise à jour, la suppression et la récupération d'utilisateurs. Sans tests unitaires, il serait difficile de s'assurer que chaque fonctionnalité fonctionne correctement sans causer des erreurs lors de l'utilisation du système.

Prerequis

Pour suivre ce tutoriel, vous aurez besoin des éléments suivants :

  • Java JDK 11 ou ultérieur
  • Apache Maven ou Gradle pour la gestion des dépendances et le build
  • Un éditeur de code (IntelliJ IDEA, Eclipse, VS Code)
  • Connaissance de base en Spring Boot

Concepts fondamentaux

1. Qu'est-ce que JUnit ?

JUnit est un framework de test Java qui permet d'écrire et d'exécuter des tests unitaires pour vérifier le bon fonctionnement du code. Il facilite la création de méthodes de test, leur exécution et l'affichage des résultats.

// Exemple de classe de test JUnit
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class UserServiceTest {

    @Test
    public void testCreateUser() {
        // Création d'une instance du service utilisateur
        UserService userService = new UserService();

        // Création d'un utilisateur et appel de la méthode createUser
        User user = userService.createUser("John Doe", "john.doe@example.com");

        // Vérification que l'utilisateur a été créé avec succès
        assertEquals("John Doe", user.getName());
        assertEquals("john.doe@example.com", user.getEmail());
    }
}

2. Annotatifs JUnit

JUnit utilise des annotations pour décrire les méthodes de test et leur comportement. Voici quelques-unes des annotations les plus courantes :

  • @Test : Indique que la méthode est une méthode de test.
  • @BeforeEach : Exécute avant chaque méthode de test.
  • @AfterEach : Exécute après chaque méthode de test.
  • @BeforeAll : Exécute une seule fois avant toutes les méthodes de test.
  • @AfterAll : Exécute une seule fois après toutes les méthodes de test.
// Exemple d'utilisation des annotations @BeforeEach et @AfterEach
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

public class UserServiceTest {

    private UserService userService;

    @BeforeEach
    public void setUp() {
        // Initialisation du service utilisateur avant chaque test
        userService = new UserService();
    }

    @AfterEach
    public void tearDown() {
        // Nettoyage après chaque test
        userService = null;
    }

    @Test
    public void testCreateUser() {
        User user = userService.createUser("John Doe", "john.doe@example.com");
        assertEquals("John Doe", user.getName());
        assertEquals("john.doe@example.com", user.getEmail());
    }
}

3. Assertions JUnit

JUnit propose une variété d'assertions pour vérifier les conditions dans les méthodes de test :

  • assertEquals(expected, actual) : Vérifie que la valeur attendue est égale à la valeur réelle.
  • assertTrue(condition) : Vérifie que la condition est vraie.
  • assertFalse(condition) : Vérifie que la condition est fausse.
  • assertNull(value) : Vérifie que la valeur est null.
// Exemple d'utilisation des assertions JUnit
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class UserServiceTest {

    @Test
    public void testUpdateUser() {
        User user = new User("John Doe", "john.doe@example.com");
        userService.updateUser(user);

        assertEquals("Updated John Doe", user.getName());
        assertTrue(user.isActive());
    }
}

4. Mocking avec Mockito

Mockito est un framework de mocking Java qui permet de créer des objets simulés (mocks) pour tester les classes en isolant leurs dépendances.

// Exemple d'utilisation de Mockito pour le mocking
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

public class UserServiceTest {

    @Test
    public void testDeleteUser() {
        // Création d'un mock du repository utilisateur
        UserRepository userRepository = mock(UserRepository.class);
        UserService userService = new UserService(userRepository);

        User user = new User("John Doe", "john.doe@example.com");
        when(userRepository.findById(1L)).thenReturn(Optional.of(user));

        userService.deleteUser(1L);

        verify(userRepository, times(1)).deleteById(1L);
    }
}

Mise en pratique : projet fil rouge

Nous allons créer un mini-projet complet et réaliste : un gestionnaire de tâches. Ce projet comprendra les fonctionnalités suivantes :

  • Ajouter une tâche
  • Mettre à jour une tâche
  • Supprimer une tâche
  • Récupérer toutes les tâches

Étape 1 : Création du modèle Task

// src/main/java/com/example/todo/Task.java
package com.example.todo;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Task {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String description;
    private boolean completed;

    // Getters and Setters
}

Étape 2 : Création du service TaskService

// src/main/java/com/example/todo/TaskService.java
package com.example.todo;

import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Service
public class TaskService {
    private List<Task> tasks = new ArrayList<>();

    public Task addTask(Task task) {
        tasks.add(task);
        return task;
    }

    public Task updateTask(Long id, Task updatedTask) {
        for (int i = 0; i < tasks.size(); i++) {
            if (tasks.get(i).getId().equals(id)) {
                tasks.set(i, updatedTask);
                break;
            }
        }
        return updatedTask;
    }

    public void deleteTask(Long id) {
        tasks.removeIf(task -> task.getId().equals(id));
    }

    public List<Task> getAllTasks() {
        return new ArrayList<>(tasks);
    }

    public Optional<Task> getTaskById(Long id) {
        return tasks.stream()
                .filter(task -> task.getId().equals(id))
                .findFirst();
    }
}

Étape 3 : Création du contrôleur TaskController

// src/main/java/com/example/todo/TaskController.java
package com.example.todo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping("/tasks")
public class TaskController {
    @Autowired
    private TaskService taskService;

    @PostMapping
    public Task addTask(@RequestBody Task task) {
        return taskService.addTask(task);
    }

    @PutMapping("/{id}")
    public Task updateTask(@PathVariable Long id, @RequestBody Task updatedTask) {
        return taskService.updateTask(id, updatedTask);
    }

    @DeleteMapping("/{id}")
    public void deleteTask(@PathVariable Long id) {
        taskService.deleteTask(id);
    }

    @GetMapping
    public List<Task> getAllTasks() {
        return taskService.getAllTasks();
    }

    @GetMapping("/{id}")
    public Optional<Task> getTaskById(@PathVariable Long id) {
        return taskService.getTaskById(id);
    }
}

Étape 4 : Création du service de test TaskServiceTest

// src/test/java/com/example/todo/TaskServiceTest.java
package com.example.todo;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class TaskServiceTest {

    private TaskService taskService;

    @BeforeEach
    public void setUp() {
        taskService = new TaskService();
    }

    @Test
    public void testAddTask() {
        Task task = new Task("Buy groceries", false);
        Task result = taskService.addTask(task);
        assertEquals("Buy groceries", result.getDescription());
        assertFalse(result.isCompleted());
    }

    @Test
    public void testUpdateTask() {
        Task task = new Task("Buy groceries", false);
        taskService.addTask(task);

        Task updatedTask = new Task("Buy groceries and milk", true);
        Task result = taskService.updateTask(1L, updatedTask);
        assertEquals("Buy groceries and milk", result.getDescription());
        assertTrue(result.isCompleted());
    }

    @Test
    public void testDeleteTask() {
        Task task = new Task("Buy groceries", false);
        taskService.addTask(task);

        taskService.deleteTask(1L);
        List<Task> tasks = taskService.getAllTasks();
        assertEquals(0, tasks.size());
    }

    @Test
    public void testGetAllTasks() {
        Task task1 = new Task("Buy groceries", false);
        Task task2 = new Task("Clean the house", false);
        taskService.addTask(task1);
        taskService.addTask(task2);

        List<Task> tasks = taskService.getAllTasks();
        assertEquals(2, tasks.size());
    }

    @Test
    public void testGetTaskById() {
        Task task = new Task("Buy groceries", false);
        taskService.addTask(task);

        Optional<Task> result = taskService.getTaskById(1L);
        assertTrue(result.isPresent());
        assertEquals("Buy groceries", result.get().getDescription());
    }
}

Étape 5 : Création du contrôleur de test TaskControllerTest

// src/test/java/com/example/todo/TaskControllerTest.java
package com.example.todo;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

public class TaskControllerTest extends IntegrationTestBase {

    @BeforeEach
    public void setUp() {
        // Initialisation des données avant chaque test
    }

    @Test
    public void testAddTask() throws Exception {
        mockMvc.perform(post("/tasks")
                .contentType("application/json")
                .content("{\"description\":\"Buy groceries\",\"completed\":false}"))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.description").value("Buy groceries"))
                .andExpect(jsonPath("$.completed").value(false));
    }

    @Test
    public void testUpdateTask() throws Exception {
        mockMvc.perform(put("/tasks/1")
                .contentType("application/json")
                .content("{\"description\":\"Buy groceries and milk\",\"completed\":true}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.description").value("Buy groceries and milk"))
                .andExpect(jsonPath("$.completed").value(true));
    }

    @Test
    public void testDeleteTask() throws Exception {
        mockMvc.perform(delete("/tasks/1"))
                .andExpect(status().isNoContent());
    }

    @Test
    public void testGetAllTasks() throws Exception {
        mockMvc.perform(get("/tasks"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.[0].description").value("Buy groceries"))
                .andExpect(jsonPath("$.[0].completed").value(false));
    }

    @Test
    public void testGetTaskById() throws Exception {
        mockMvc.perform(get("/tasks/1"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.description").value("Buy groceries"))
                .andExpect(jsonPath("$.completed").value(false));
    }
}

Étape 6 : Configuration de la base de données en mémoire

// src/test/resources/application.properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true

Erreurs frequentes et debugging

1. java.lang.NoClassDefFoundError: org/mockito/Mockito

Cette erreur se produit lorsque Mockito n'est pas correctement ajouté à votre projet.

## ❌ Mauvais
// pom.xml
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.12.4</version>
    <scope>test</scope>
</dependency>

## ✅ Correct
// pom.xml
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.0.0</version>
    <scope>test</scope>
</dependency>

2. java.lang.AssertionError: Expected :false, but was :true

Cette erreur se produit lorsque l'assertion assertTrue échoue.

## ❌ Mauvais
// src/test/java/com/example/todo/TaskServiceTest.java
@Test
public void testUpdateTask() {
    Task task = new Task("Buy groceries", false);
    taskService.addTask(task);

    Task updatedTask = new Task("Buy groceries and milk", true);
    Task result = taskService.updateTask(1L, updatedTask);
    assertFalse(result.isCompleted());
}

## ✅ Correct
// src/test/java/com/example/todo/TaskServiceTest.java
@Test
public void testUpdateTask() {
    Task task = new Task("Buy groceries", false);
    taskService.addTask(task);

    Task updatedTask = new Task("Buy groceries and milk", true);
    Task result = taskService.updateTask(1L, updatedTask);
    assertTrue(result.isCompleted());
}

3. java.lang.NullPointerException: Cannot invoke "com.example.todo.TaskService.deleteTask(java.lang.Long)" because "this.taskService" is null

Cette erreur se produit lorsque le service n'est pas correctement injecté dans le contrôleur de test.

// src/test/java/com/example/todo/TaskControllerTest.java
@Autowired
private TaskService taskService; // Ajouter @Autowired pour l'injection du service

@BeforeEach
public void setUp() {
    taskService = new TaskService(); // Supprimer cette ligne
}

Pour aller plus loin

  1. Tests d'intégration : Apprenez comment tester les interactions entre différentes couches et composants de votre application.

  2. Tests end-to-end (E2E) : Utilisez Selenium pour écrire des tests qui simulent une interaction complète avec l'application.

  3. Coverage et rapports de test : Configurez un outil pour générer des rapports de couverture de code et analyser les résultats.

Défi pratique

Créez un gestionnaire simple d'utilisateurs qui permet de créer, mettre à jour, supprimer et récupérer des utilisateurs. Assurez-vous d'écrire des tests unitaires pour chacune des fonctionnalités.

Indice : Utilisez une classe User avec les attributs id, name, et email. Créez un service UserService avec des méthodes correspondantes, puis écrivez des tests unitaires pour vérifier le bon fonctionnement de chaque méthode.

Besoin d'aide sur Spring Boot ?

Besoin d'aide sur un projet technique ? Decrivez-le pour des conseils personnalises.

Recevoir des conseils

Questions frequentes

Quelles sont les principales annotations utilisées pour tester des services Spring Boot avec JUnit ?
Les principales annotations utilisées comprennent @SpringBootTest pour lancer un environnement entier, @Autowired pour injecter des dépendances, et @Test pour marquer les méthodes de test.
Comment configurer le mock d'un service dans une classe de test Spring Boot ?
Pour configurer un mock d'un service, utilisez la classe @MockBean. Cela permet de remplacer le comportement réel du service par celui que vous définissez dans les tests.
Quelle est l'importance des vues thymeleaf en testant une application Spring Boot ?
Les vues Thymeleaf sont importantes lors des tests pour vérifier la structure et le contenu de la réponse HTTP. Utilisez des bibliothèques comme Thymeleaf Testing pour faciliter ces tests.

Pages liees

Chaque semaine, le meilleur de la tech francaise

Tendances, salaires, outils et opportunites — directement dans votre boite mail.

Gratuit. Desabonnement en un clic. Pas de spam.