Junit 5 : pourquoi et comment migrer

J’ai récemment eu l’opportunité de migrer un projet de Junit 4 à Junit 5. J’en profite donc pour vous faire un post sur pourquoi et comment migrer.

Des bonnes raisons pour migrer

Le première raison de migrer peut tout simplement être pour maintenir sa stack technique à jour. Je ne sais pas pour vous, mais personnellement j’ai trouvé ça super intéressant de travailler sur une technologie plus récente. Le premier effet sera donc de rendre heureux vos développeurs.

L’autre avantage à avoir une stack technique récente c’est d’augmenter vos chances d’attirer et de retenir des développeurs dans votre équipe. Je ne connais pas grand monde qui adore travailler sur un projet legacy qui traîne une stack qui date du paléolithique.

D’autres bonnes raisons pour migrer

Si l’on regarde un peu l’historique de Junit, on voit que Junit 3 est sorti on début des années 2000. C’est à dire avant la sortie de Java 5 en 2004. La première version de Junit 4 et sortie en 2006 et incluait entre autres choses le support des annotations.

La dernière version qui est la 4.12 est elle sortie en 2014 et ne prends pas en compte les nouvelles possibilités offertent par Java 8.

Une des motivations derrière la version 5 de Junit est donc d’utiliser les nouveautés de Java 8 et en particulier les lambdas. Il y a d’autres améliorations vraiment chouettes que je présenterai dans un autre article

Les différents modules de Junit 5

Une des évolutions notables de Junit 5 est la séparation entre le modèle qui permet de définir des tests (la syntaxe en gros) et la plateforme en charge d’exécuter les tests.

Ceci ce traduit par différents modules disponibles :

Junit Platform 1.5.2

La plateforme fournit un moteur pour lancer des tests sur la JVM.

Junit Jupiter 5.5.2

C’est le modèle (ainsi que certaines extensions) pour écrire des tests.

Junit Vintage 5.5.2

C’est un modèle qui permet de lancer sans trop d’effort des tests écrits pour Junit 3 ou Junit 4.

La méthode de fainéant

Une approche possible est donc d’utiliser Junit Vintage pour pouvoir exécuter ses tests existants sur la plateforme Junit 5.
On met à jour son pom.xml ou son Gradle si on est plus moderne:

<dependency>   
   <groupId>org.junit.jupiter</groupId>   
   <artifactId>junit-jupiter-engine</artifactId>  
   <version>5.5.2</version>   
 </dependency>

<dependency>  
   <groupId>org.junit.vintage</groupId>  
   <artifactId>junit-vintage-engine</artifactId>  
   <version>5.5.2</version>   
 </dependency>

On relance le tout en ligne de commande ou dans son IDE préféré et normalement ça fonctionne à peu près. Seulement c’est un peu frustrant et de mon expérience vous ne pourrez pas faire cohabiter des tests Junit 5 et Junit 4.

La méthode plus sérieuse

La méthode plus sérieuse consiste à utiliser directement Junit 5 mais pour cela il va falloir mettre à jour vos tests.
Première surprise, la plupart des annotations ont été renommées. Alors oui c’est surprenant et je vous entends déjà raler. Mais d’une part ça se fait très bien avec un gros rechercher / remplacer et d’autre part je trouve que c’est plutôt malin. Dans le détail ça donne ça :

Junit 4Junit 5
@Test@Test
@Before@BeforeEach
@BeforeClass@BeforeAll
@After@AfterEach
@AfterClass@AfterAll
@Ignore@Disabled

Prenons par exemple le cas du @BeforeClass. Auparavant un débutant sur Junit pouvait avoir un peu de mal à comprendre le fonctionnement exact de cette annotation. Maintenant c’est beaucoup plus explicite avec @BeforeEach.

Gestion des exceptions

L’autre gros changement de compatibilité concerne la gestion des exceptions.
Les deux syntaxes suivantes ont été supprimées :

@Test(expected=BusinessLogicException.class)

@Rule  
public ExpectedException expectedException = ExpectedException.none();

Il va vous falloir utiliser maintenant cette syntaxe :

Throwable exception = assertThrows(BusinessLogicException.class,  
    () -> userService.getUserDetails(1337));

On voit de suite la prise en compte des nouveautés Java 8 puisque le second paramètre ici est une lambda !
Vous pouvez ensuite faire ce que vous souhaitez avec l’exception, comme par exemple utiliser des assertions :

assertThat(exception.getMessage(), is("Could not get user details!"));

Intégration avec Mockito

L’intégration avec Mockito va aussi nécessiter quelques changements.
En effet on passe d’une notion de Runner en Junit 4 à une notion d’Extension en Junit 5.

La syntaxe qui était ainsi :

@RunWith(MockitoJUnitRunner.class)

Va donc ce transformer en :

@ExtendWith(MockitoExtension.class)

Unnecessary Stubbing

L’autre problème qui peut se produire est que cela va vous obliger à utiliser la version la plus récente de Mockito. Si ce n’était pas déjà le cas vous risquez de vous retrouvez avec des erreurs de type :

org.mockito.exceptions.misusing.UnnecessaryStubbingException:

Unnecessary stubbings detected.

Clean & maintainable test code requires zero unnecessary code.

Following stubbings are unnecessary (click to navigate to relevant line of code):

1\. -> at org.github.jbleduigou.users.UserServiceImplTest.setUp(UserServiceImplTest.java:83)

Please remove unnecessary stubbings or use 'lenient' strictness.   
More info: javadoc for UnnecessaryStubbingException class.

Pourquoi cela se produit ? Les dernières versions de Mockito interdisent de préparer des stubbings qui ne sont ensuite pas utilisés dans les tests.

Pour faire disparaître l’erreur vous pouvez soit préfixer votre instruction par lenient() :

lenient().when(mockDao.getUserDetails()).thenReturn(details);

Soit améliorer vos tests afin de ne déclarer que les stubbings qui seront réellement utilisés.

Bilan

Au final on se rend compte que la migration n’est pas aussi simple que de mettre à jour une ou deux lignes dans un fichier pom.

Personnellement j’ai mis environ deux jours pour un projet comportant environ 14000 lignes de code de production et autant de lignes de tests.
Je n’ai rien eu à faire en revanche au niveau de l’intégration continue. Le format des résultats des tests étant le même, Jenkins a continué à exploiter ses résultats comme avant. Idem pour la couverture de code.

Alors vous migrez quand vous ?


J’espère que cet article vous a été utile !
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.
Photo de couverture par Lucas Sankey.
Cet article a initialement été publié sur Medium.