Junit : l’exception qui confirme la règle

Jean-Baptiste Le Duigou
Jul 10 · 3 min read

Lorsque que l’on devient plus à l’aise avec l’utilisation de Junit il arrive un moment où l’on souhaite tester un scénario qui doit retourner une exception. Il existe différentes approches possibles pour le faire, chacune ayant ses avantages et ses inconvénients.

Un bon test d’exception c’est quoi ?

Avant de vous montrer les différentes approches possibles je souhaite tout d’abord revenir sur le besoin.

On va attendre de notre test qu’il :

  • Réussisse si le code testé renvoie la bonne exception
  • Échoue si le code testé renvoie la mauvaise exception
  • Échoue si le code testé ne renvoie pas d’exception

Le code testé

Les exemples que je vais vous présenter aujourd’hui testeront la méthode getBeerById() de la classe BeerService.

Pour rappel voici le code de cette classe :

L’approche brut de pomme 🍎

La solution qui vient en tête en premier consiste à catcher l’exception et à asserter cette expression.

Les trois critères que nous avions défini sont satisfaits et on peut donc dire que cette approche fonctionne.
Le seul problème à mon goût est la lourdeur de la syntaxe, il y a moyen de faire mieux.

L’approche par annotation 😒

L’autre possibilité, un poil plus élégante est d’utiliser les annotations.

La syntaxe est nettement plus allégée et encore une fois les trois critères sont satisfaits.

Dans le cas où ce n’est pas le bon type d’exception nous avons bien un test qui échoue :

java.lang.Exception: Unexpected exception, expected<com.github.jbleduigou.beer.exception.EntityNotFoundException> but was<java.lang.IllegalArgumentException>at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:28)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.rules.ExpectedException$ExpectedExceptionStatement.evaluate(ExpectedException.java:239)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)

Si aucune exception n’est retournée nous avons là encore un test qui échoue :

java.lang.AssertionError: Expected exception: com.github.jbleduigou.beer.exception.EntityNotFoundException

Le principal reproche que l’on peut faire c’est qu’il n’est plus possible d’asserter sur le message de l’exception. Le message d’erreur lorsque qu’il manque une exception n’est pas non plus très explicite.

L’approche par @Rule 😃

La solution que je préfère personnellement est l’utilisation d’une Rule.
Voici à quoi cela ressemble :

On commence par déclarer une règle de type ExpectedException.
Ensuite dans le test on précise le type d’exception attendue et/ou le message attendu.

La syntaxe est à la fois allégée et explicite.

Dans le cas où ce n’est pas le bon type d’exception nous avons bien un test qui échoue :

java.lang.AssertionError: 
Expected: (exception with message a string containing "Beer with id=3457 not found" and an instance of com.github.jbleduigou.beer.exception.EntityNotFoundException)
but: an instance of com.github.jbleduigou.beer.exception.EntityNotFoundException <java.lang.IllegalArgumentException: Beer with id=3457 not found> is a java.lang.IllegalArgumentException

Si aucune exception n’est retournée nous avons un test qui échoue :

java.lang.AssertionError: Expected test to throw (exception with message a string containing "Beer with id=3457 not found" and an instance of com.github.jbleduigou.beer.exception.EntityNotFoundException)

Junit 5, ca ne marche plus ! 😮

Si vous êtes passé à Junit 5 vous constaterez qu’il n’est plus possible d’utiliser ni l’approche par annotation ni l’approche par rule. C’est d’ailleurs une des choses qui m’a pris le plus de temps lors de la migration à Junit 5 sur un projet récemment.

La syntaxe qui est recommandée avec cette version est l’utilisation de assertThrows. Concrètement ça ressemble à ça :

Le test réussi quand l’exception est renvoyée correctement.

En cas de message d’erreur erroné c’est le assertThat qui va générer une erreur :

java.lang.AssertionError: 
Expected: is "Panic!"
but: was "Beer with id=3457 not found"

Dans le cas où ce n’est pas le bon type d’exception s’est le assertThrows qui réagit :

org.opentest4j.AssertionFailedError: Unexpected exception type thrown ==> expected: <java.lang.IllegalArgumentException> but was: <com.github.jbleduigou.beer.exception.EntityNotFoundException>at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:65)

Enfin si aucune exception n’est renvoyée s’est également le assertThrows qui nous prévient :

org.opentest4j.AssertionFailedError: Expected com.github.jbleduigou.beer.exception.EntityNotFoundException to be thrown, but nothing was thrown.at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:71)

Et voilà vous en savez maintenant plus sur la gestion des exceptions avec Junit.

N’hésitez pas à me faire part de vos commentaires ou questions en bas de cet article ou en m’envoyant un message sur LinkedIn :
http://www.linkedin.com/in/jbleduigou/en

Le code des exemples utilisés dans cet article est disponible sur GitHub : https://github.com/jbleduigou/beer-api-java

Jean-Baptiste Le Duigou

Written by

Senior Full-Stack Developer, Java Expert & Golang Enthusiast https://github.com/jbleduigou

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade