Introduction : « Pourquoi mon test échoue-t-il alors qu'il devrait réussir ? »
\n\nAvez-vous déjà écrit un test unitaire qui devait attraper une exception, mais qui échouait au contraire avec une erreur rouge ? Cela vous dit quelque chose ? La question « How do you assert that a certain exception is thrown in JUnit tests? » sur Stack Overflow a rassemblé des milliers de vues et des centaines de commentaires. Et ce n'est pas un hasard : tester les exceptions est l'un de ces sujets où même les développeurs expérimentés trébuchent parfois.
\n\nEn 2025, alors que la qualité du code est devenue un facteur critique (en particulier dans les microservices et les pipelines d'IA), savoir vérifier correctement les exceptions n'est pas simplement une « bonne pratique », mais une compétence de base. Dans cet article, nous allons analyser toutes les méthodes actuelles pour asserter les exceptions dans JUnit 5, montrer les pièges à éviter et fournir une liste de contrôle des meilleures pratiques. C'est parti !
\n\nPourquoi est-il important de tester les exceptions ?
\n\nImaginez : vous écrivez une méthode divide(int a, int b). Si b == 0, la méthode doit lancer une ArithmeticException. Sans test sur ce comportement, vous risquez qu'un mois plus tard, un autre développeur (ou vous-même) supprime accidentellement la vérification, et que l'application plante en production avec une erreur incompréhensible.
Les tests sur les exceptions sont votre filet de sécurité. Ils documentent le comportement attendu du système dans les cas limites. Et JUnit 5 offre plusieurs outils élégants pour cela.
\n\nMéthode 1 : assertThrows — le roi des tests modernes
\n\nDepuis JUnit 5 (et son package org.junit.jupiter.api), la méthode assertThrows est devenue le standard de facto. Pourquoi ? Parce qu'elle est concise, lisible et fonctionnelle.
import static org.junit.jupiter.api.Assertions.assertThrows;\n\n@Test\nvoid whenDivideByZero_thenArithmeticException() {\n assertThrows(ArithmeticException.class, \n () -> calculator.divide(10, 0));\n}\n\n\nQue se passe-t-il ici ? assertThrows prend la classe de l'exception attendue et une lambda avec le code qui doit la lever. Si l'exception est levée, le test est vert. Sinon, il est rouge. Simple et clair.
Variante avancée : vérifier le message de l'exception
\n\nParfois, il ne suffit pas de vérifier le type de l'exception — il faut s'assurer que le message contient le texte correct. assertThrows retourne l'objet exception, et vous pouvez vérifier ses propriétés :
import static org.junit.jupiter.api.Assertions.*;\n\n@Test\nvoid whenInvalidEmail_thenExceptionWithMessage() {\n IllegalArgumentException exception = assertThrows(\n IllegalArgumentException.class,\n () -> userService.register("invalid-email")\n );\n \n assertTrue(exception.getMessage().contains("Email invalide"));\n}\n\n\nCette approche est particulièrement utile pour tester des exceptions personnalisées avec des champs (par exemple, un code d'erreur).
\n\nMéthode 2 : Le bon vieux expected (legacy JUnit 4)
\n\nSi vous travaillez avec JUnit 4 ou maintenez du code legacy, vous avez probablement rencontré l'attribut expected dans l'annotation @Test :
@Test(expected = ArithmeticException.class)\npublic void whenDivideByZero_thenException() {\n calculator.divide(10, 0);\n}\n\n\nInconvénient : cette méthode ne permet pas de vérifier le message de l'exception ou ses propriétés. De plus, si l'exception est levée plus tôt que prévu (par exemple, dans la ligne d'initialisation), le test peut passer faussement. En 2025, nous recommandons d'éviter cette approche dans le nouveau code.
\n\nMéthode 3 : Try-catch avec un indicateur (pour les amateurs de contrôle)
\n\nAvant l'arrivée de JUnit 5, beaucoup utilisaient une construction avec try-catch et un indicateur booléen. Cela semble lourd, mais offre un contrôle total :
@Test\nvoid whenDivideByZero_thenExceptionCaught() {\n boolean thrown = false;\n try {\n calculator.divide(10, 0);\n } catch (Arithmeti