Le logger de la mort
Parfois, au cours d'une mission chez un client, on tombe sur du code pas testé, et pas toujours prévu pour être testé non plus. Je vais présenter un cas sur lequel je suis tombé récemment, issu d'un assez gros projet Java EE en cours de développement.
Une classe de services X effectue une certain nombre de traitements. En cas d’erreur, au lieu de jeter une exception métier, un code d’erreur est loggué. Impossible d’utiliser un @Test(expected=...)
, donc. Fort heureusement, le champ logger dans X est une interface, potentiellement mockable :
Dans tous les cas où nous sommes intéressés, la méthode logError est appelée avec un paramètre Key représentant le code d’erreur. Malheureusement, le logger n’est pas injecté dans un setter, et il est privé. On aimerait tout de même vérifier que l’erreur est bien logguée sous certaines conditions. Solution, un peu de réflexion pour forcer le champ logger à une instance que l’on contrôle :
La méthode resetLogCalls
permet d’utiliser la même instance du logger sur plusieurs tests, il faut l’appeler dans une méthode annotée @After
.
Il faut maintenant injecter notre propre logger dans le service testé :
Et voila, nous pouvons maintenant faire des assertTrue
utilisant la méthode wasLogged
et la Key nous intéressant ! Notez l’appel à Field#setAccessible(boolean)
pour contourner le caractère private du champ logger
(que l’on restaure par la suite).
Note : si le logger n’avait pas utilisé d’interface, nous aurions pu nous en sortir en créant une classe fille redéfinissant toutes les méthodes sans appels à super()
.