Lorsqu’on écrit un test unitaire avec JUnit pour lequel on s’attend à ce qu’une exception soit levée, on utilise l’attribut expected de l’annotation @Test.

@Test(expected = FooException.class)
void testFoo() {
    ...
}

L’exception interrompt le flux d’exécution du programme et ainsi le code situé après la méthode qui lève (ou propage) l’exception ne sera jamais exécuté.

Néanmoins, on peut souhaiter dans les tests qu’une partie du code soit exécuté malgré l’exception pour vérifier une valeur ou une assertion.

Une solution consisterait à catcher l’exception de façon à contrôler le flux d’exécution du programme et réaliser les tests souhaités. Cela nécessite l’ajout d’un test supplémentaire pour s’assurer que l’exception a bien été levée et peut se faire en vérifiant qu’une instruction placée directement après la méthode levant l’exception n’est jamais exécutée : Assert.fail().

@Test
void testFoo() {

  MapFinder mapFinder = new MapFinder();

  mapFinder.walk(10);

  Assert.assertEquals(10, map.getPosition());

  try {
     mapFinder.walk(-42);  // doit lever une exception
     Assert.fail(“Une exception aurait du être levée”);
  } catch (FooException x) {
     // On doit passer par là
  }
  Assert.assertEquals(10, map.getPosition());
}

Dans l’exemple ci-dessus, on s’assure que l’état de la variable n’a pas été modifié lorsque l’exception est levée (getPosition() retourne 10). L’inconvénient est qu’on ne bénéficie plus des facilités de JUnit avec l’attribut expected de l’annotation @Test.

On peut être amené à valider autre chose que des assertions. Par exemple lorsqu’on utilise un framework de mock et que l’on souhaite vérifier que le mock a été utilisé comme prévu. Avec EasyMock, on utilisera la méthode EasyMock.verify(myMock).

Lorsque je souhaite combiner les deux vérifications (levée d’exception par l’objet testé et les appels au mock), j’utilise un bloc try/finally.

Ce qui nous donne comme code :

@Test(expected = FooException.class)
void testFoo() {
  MapFinder mapFinder = new MapFinder();

  // Initialisation du mock
  Map mapMock = EasyMock.createMock(Map.class);
  mapFinder.set(map);

  // Définition du comportement du mock
  ...
  replay(mapMock);

  mapFinder.walk(10);

  Assert.assertEquals(10, map.getPosition());

  try {
     mapFinder.walk(-42);  // doit lever une exception
  } finally {
 Assert.assertEquals(10, map.getPosition());
     verify(dependencyMock);
  }

}

En se basant sur l’attribut expected , on met en évidence le comportement principal attendu dans ce cas de test tout en bénéficiant des facilités de JUnit. Par ailleurs,  le block try/finally permet de réaliser tous les tests supplémentaires en sus de l’exception.