Bonjour ! Dans la leçon précédente, nous avons appris à structurer nos tests de manière propre et isolée grâce aux annotations de cycle de vie comme @BeforeEach et @AfterEach. Vous savez maintenant comment garantir que chaque test s'exécute dans un environnement contrôlé.
Aujourd'hui, nous allons nous concentrer sur un aspect tout aussi crucial de la robustesse d'une application : la gestion des erreurs. Un bon code ne se contente pas de fonctionner avec des données valides (le "happy path"), il doit aussi réagir correctement face à des données invalides. L'objectif de cette leçon est de vous apprendre à tester que votre code lève bien les exceptions attendues dans ces cas d'erreur, en utilisant la méthode assertThrows de JUnit.
1. Pourquoi tester les exceptions ?
En tant que développeur, vous savez que les utilisateurs ou d'autres systèmes peuvent fournir des données inattendues. Une application robuste doit anticiper ces scénarios. Plutôt que de laisser l'application planter, une bonne pratique est de lancer une exception pour signaler un problème.
Par exemple :
- Tenter de diviser un nombre par zéro.
- Chercher un utilisateur en base de données avec un ID qui n'existe pas.
- Créer un objet avec un paramètre invalide (par exemple, un nom de produit vide).
Vos tests unitaires doivent couvrir ces "unhappy paths" (scénarios d'erreur) pour garantir que votre code est non seulement correct, mais aussi résilient. Ne pas tester les cas d'erreur, c'est laisser une porte ouverte à des bugs en production.
2. La méthode assertThrows
Pour vérifier qu'une portion de code lève bien une exception, JUnit 5 fournit une méthode d'assertion très élégante : assertThrows.

La syntaxe de assertThrows est la suivante :
assertThrows(Class<T> expectedException, Executable executable)
Décortiquons ses deux arguments :
expectedException: La classe de l'exception que l'on s'attend à voir levée. Par exemple,IllegalArgumentException.class.executable: Le bloc de code qui est censé provoquer l'exception. Pour cela, on utilise une expression lambda.
Le rôle crucial de l'expression lambda
Vous vous demandez peut-être pourquoi on utilise la syntaxe () -> monCodeQuiPlante().
Si vous écriviez assertThrows(IllegalArgumentException.class, service.maMethode(valeurInvalide)), la méthode maMethode serait exécutée avant l'appel à assertThrows. L'exception serait donc levée immédiatement, faisant échouer le test sans que assertThrows n'ait eu la chance de la "capturer".
La lambda () -> ... encapsule l'appel de votre méthode. Elle ne l'exécute pas tout de suite. Elle passe à assertThrows une "recette" pour exécuter le code. En interne, assertThrows peut alors l'envelopper dans un bloc try-catch pour vérifier si la bonne exception est bien attrapée.
3. Mise en pratique : Tester une division par zéro
L'exemple le plus classique est la division par zéro. Voyons comment le tester concrètement. Le tutoriel suivant vous guide pas à pas.
Utilisation de assertThrows dans JUnit 5 pour les Tests d ...
Cet article de Byoskill montre un exemple très clair et concis de test d'exception.
Lisez les sections suivantes : "Création d'une classe avec une méthode susceptible de lever une exception": Repérez comment la classe Calculatrice est implémentée, en particulier la méthode diviser qui lance une IllegalArgumentException si le diviseur est zéro. "Création d'un test utilisant assertThrows": Analysez la classe de test CalculatriceTest. Concentrez-vous sur la méthode testDivisionParZero et la syntaxe de assertThrows pour vérifier que l'exception est bien levée.
Comme vous l'avez lu, le test est simple et direct :
@Test
public void testDivisionParZero() {
Calculatrice calculatrice = new Calculatrice();
// On affirme (assert) que l'exécution de calculatrice.diviser(10, 0)
// va lever (throws) une exception de type IllegalArgumentException.
assertThrows(IllegalArgumentException.class, () -> {
calculatrice.diviser(10, 0);
});
}
Si l'exception est levée, le test passe (vert). Si aucune exception n'est levée, ou si une autre sorte d'exception est levée, le test échoue (rouge).
4. Démonstration en vidéo
Pour voir ce concept en action, la vidéo ci-dessous montre un autre exemple pratique. Le présentateur teste une classe Character et vérifie qu'une exception est levée si le nom du personnage est invalide.
Tutoriel Java - tests unitaires
Cette vidéo, que vous avez déjà consultée pour d'autres concepts, montre ici l'utilisation de assertThrows.
Regardez la portion de test d'exception. Observez comment le présentateur : Identifie un cas d'erreur à tester (un nom de personnage trop court). Utilise assertThrows avec le type d'exception attendu (IllegalArgumentException.class). Passe le code qui doit échouer via une expression lambda. Exécute les tests et analyse le résultat.
5. Capturer et inspecter l'exception (Avancé mais utile)
Parfois, il ne suffit pas de savoir qu'une exception a été levée. On veut aussi vérifier son message. assertThrows retourne l'instance de l'exception capturée, ce qui vous permet de faire des assertions supplémentaires sur elle.

Voici comment vous pourriez faire pour l'exemple de la calculatrice :
@Test
public void testDivisionParZeroAvecMessage() {
Calculatrice calculatrice = new Calculatrice();
// 1. On capture l'exception retournée par assertThrows
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> {
calculatrice.diviser(10, 0);
}
);
// 2. On fait une assertion sur le message de l'exception
String expectedMessage = "Division par zéro";
String actualMessage = exception.getMessage();
assertEquals(expectedMessage, actualMessage);
}
C'est une technique très puissante pour garantir que vos messages d'erreur sont clairs et corrects, ce qui est essentiel pour le débogage en production.
6. Exercice : Rendez votre ProductService plus robuste
Il est temps de mettre la main à la pâte. Dans les leçons précédentes, notre ProductService.findById() retournait null si un produit n'était pas trouvé. Bien que ce soit une approche valide, une autre approche courante dans les API REST est de lever une exception.
Votre mission :
-
Créez une exception personnalisée :
- Dans votre projet, créez une nouvelle classe nommée
ProductNotFoundException. - Faites-la hériter de
RuntimeException(nous verrons la différence entrecheckedetuncheckedexceptions plus tard,RuntimeExceptionest un bon choix pour commencer). - Ajoutez un constructeur qui prend un message en paramètre et l'envoie au constructeur de la classe mère
super(message);.
public class ProductNotFoundException extends RuntimeException { public ProductNotFoundException(String message) { super(message); } } - Dans votre projet, créez une nouvelle classe nommée
-
Modifiez le
ProductService:- Ouvrez la classe
ProductService. - Dans la méthode
findById(Long id), modifiez la logique : au lieu de retournernullsi le produit n'est pas trouvé, levez votre nouvelle exception.
// Dans ProductService.java public Product findById(Long id) { // ... votre logique de recherche actuelle ... if (product == null) { // Au lieu de "return null;" throw new ProductNotFoundException("Produit non trouvé avec l'ID : " + id); } return product; } - Ouvrez la classe
-
Écrivez le test d'exception :
- Ouvrez votre classe de test
ProductServiceTest. - Créez une nouvelle méthode de test :
void testFindById_ShouldThrowException_WhenProductNotFound(). - À l'intérieur, utilisez
assertThrowspour vérifier que l'appel àservice.findById(99L)(ou tout autre ID non-existant) lève bien uneProductNotFoundException.
- Ouvrez votre classe de test
-
Nettoyez vos anciens tests :
- Votre ancien test
testFindNonExistingProductqui vérifiaitassertNullva maintenant échouer. C'est normal, le comportement du code a changé ! - Supprimez ou commentez ce test, car il ne reflète plus la logique actuelle de votre application.
- Votre ancien test
Lancez tous les tests. Ils devraient tous être au vert !
Conclusion
Félicitations ! Vous avez complété le module sur les tests unitaires avec JUnit. Vous savez désormais non seulement tester que votre code fonctionne, mais aussi qu'il échoue de manière prévisible et contrôlée.
Les points clés à retenir :
- Tester les cas d'erreur (les "unhappy paths") est fondamental pour construire des applications robustes.
assertThrowsest la méthode standard de JUnit 5 pour vérifier qu'un bloc de code lève une exception spécifique.- L'utilisation d'une expression lambda est indispensable pour que
assertThrowspuisse "capturer" et inspecter l'exception. - On peut capturer l'objet exception retourné par
assertThrowspour effectuer des vérifications supplémentaires, comme valider son message.
Dans la prochaine leçon, nous commencerons un nouveau module passionnant : la Programmation Fonctionnelle avec les Lambdas et les Streams. Vous avez déjà eu un aperçu de la puissance des lambdas avec assertThrows ; préparez-vous maintenant à les utiliser pour transformer la manière dont vous manipulez les collections de données en Java.