Logging Logger et LoggerFactory avec PowerMock et Mockito

J’ai l’enregistreur suivant que je veux supprimer, mais pour valider les entrées de journal sont appelés, pas pour le contenu.

private static Logger logger = LoggerFactory.getLogger(GoodbyeController.class); 

Je veux simuler N’IMPORTE QUELLE classe utilisée pour LoggerFactory.getLogger () mais je n’ai pas pu savoir comment faire. C’est ce à quoi je me suis retrouvé jusqu’à présent:

 @Before public void performBeforeEachTest() { PowerMockito.mockStatic(LoggerFactory.class); when(LoggerFactory.getLogger(GoodbyeController.class)). thenReturn(loggerMock); when(loggerMock.isDebugEnabled()).thenReturn(true); doNothing().when(loggerMock).error(any(Ssortingng.class)); ... } 

J’aimerais savoir:

  1. Puis-je LoggerFactory.getLogger() le LoggerFactory.getLogger() statique LoggerFactory.getLogger() pour travailler avec n’importe quelle classe?
  2. Je peux seulement sembler fonctionner when(loggerMock.isDebugEnabled()).thenReturn(true); dans le @Before et donc je ne peux pas sembler changer les caractéristiques par méthode. Y a-t-il un moyen de contourner ceci?

Modifier les résultats:

Je pensais l’avoir déjà essayé et ça n’a pas marché:

  when(LoggerFactory.getLogger(any(Class.class))).thenReturn(loggerMock); 

Mais merci, cela a fonctionné.

Cependant, j’ai essayé d’innombrables variantes pour:

 when(loggerMock.isDebugEnabled()).thenReturn(true); 

Je ne parviens pas à faire en sorte que loggerMock modifie son comportement en dehors de @Before mais cela ne se produit qu’avec Coburtura. Avec Clover, la couverture est de 100% mais il y a toujours un problème dans les deux cas.

J’ai cette classe simple:

 public ExampleService{ private static final Logger logger = LoggerFactory.getLogger(ExampleService.class); public Ssortingng getMessage() { if(logger.isDebugEnabled()){ logger.debug("isDebugEnabled"); logger.debug("isDebugEnabled"); } return "Hello world!"; } ... } 

J’ai ensuite ce test:

 @RunWith(PowerMockRunner.class) @PrepareForTest({LoggerFactory.class}) public class ExampleServiceTests { @Mock private Logger loggerMock; private ExampleServiceservice = new ExampleService(); @Before public void performBeforeEachTest() { PowerMockito.mockStatic(LoggerFactory.class); when(LoggerFactory.getLogger(any(Class.class))). thenReturn(loggerMock); //PowerMockito.verifyStatic(); // fails } @Test public void testIsDebugEnabled_True() throws Exception { when(loggerMock.isDebugEnabled()).thenReturn(true); doNothing().when(loggerMock).debug(any(Ssortingng.class)); assertThat(service.getMessage(), is("Hello null: 0")); //verify(loggerMock, atLeast(1)).isDebugEnabled(); // fails } @Test public void testIsDebugEnabled_False() throws Exception { when(loggerMock.isDebugEnabled()).thenReturn(false); doNothing().when(loggerMock).debug(any(Ssortingng.class)); assertThat(service.getMessage(), is("Hello null: 0")); //verify(loggerMock, atLeast(1)).isDebugEnabled(); // fails } } 

En trèfle, je montre une couverture de 100% du if(logger.isDebugEnabled()){ block. Mais si j’essaie de vérifier le loggerMock :

 verify(loggerMock, atLeast(1)).isDebugEnabled(); 

Je n’ai aucune interaction. J’ai également essayé PowerMockito.verifyStatic() ; dans @Before mais cela a aussi des interactions nulles.

Cela semble juste étrange que Cobertura affiche le if(logger.isDebugEnabled()){ comme n’étant pas complet à 100%, et Clover le fait, mais les deux conviennent que la vérification échoue.

@Mick, essayez également de préparer le propriétaire du champ statique, par exemple:

 @PrepareForTest({GoodbyeController.class, LoggerFactory.class}) 

EDIT1: Je viens de créer un petit exemple. Tout d’abord le contrôleur:

 import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Controller { Logger logger = LoggerFactory.getLogger(Controller.class); public void log() { logger.warn("yup"); } } 

Ensuite, le test:

 import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anySsortingng; import static org.mockito.Mockito.verify; import static org.powermock.api.mockito.PowerMockito.mock; import static org.powermock.api.mockito.PowerMockito.mockStatic; import static org.powermock.api.mockito.PowerMockito.when; @RunWith(PowerMockRunner.class) @PrepareForTest({Controller.class, LoggerFactory.class}) public class ControllerTest { @Test public void name() throws Exception { mockStatic(LoggerFactory.class); Logger logger = mock(Logger.class); when(LoggerFactory.getLogger(any(Class.class))).thenReturn(logger); new Controller().log(); verify(logger).warn(anySsortingng()); } } 

Notez les importations! Des bibliothèques remarquables dans le classpath: Mockito, PowerMock, JUnit, logback-core, logback-clasic, slf4j


EDIT2: Comme cela semble être une question populaire, je voudrais souligner que si ces messages de journal sont aussi importants et nécessitent d’être testés, c’est-à-dire qu’ils font partie intégrante du système, ils introduisent une véritable dépendance Ces journaux sont des fonctionnalités qui seraient bien meilleures dans la conception de l’ensemble du système , au lieu de s’appuyer sur le code statique d’une classe standard et technique d’un enregistreur.

Pour cette question, je recommande de créer quelque chose comme = une classe Reporter avec des méthodes telles que reportIncorrectUseOfYAndZForActionX ou reportProgressStartedForActionX . Cela aurait l’avantage de rendre la fonctionnalité visible pour toute personne lisant le code. Mais cela aidera également à réaliser des tests, à modifier les détails d’implémentation de cette fonctionnalité particulière.

Par conséquent, vous n’auriez pas besoin d’outils statiques comme PowerMock. À mon avis, le code statique peut être correct, mais dès que le test exige de vérifier ou de se moquer du comportement statique, il est nécessaire de modifier et d’introduire des dépendances claires.

Un peu tard pour la fête – je faisais quelque chose de similaire et j’avais besoin de conseils et je me suis retrouvé ici. Ne prenant aucun crédit – j’ai pris tout le code de Brice mais j’ai eu les “interactions nulles” que Cengiz a eues.

En utilisant les conseils de jheriks et de Joseph Lust, je pense que je sais pourquoi. Mon object a été testé en tant que champ et il a été mis à jour dans un avantBike. Ensuite, le véritable enregistreur n’était pas le simulacre mais une vraie classe initiée comme suggéré par Jhriks …

Je le ferais normalement pour mon object testé afin d’obtenir un nouvel object pour chaque test. Quand j’ai déplacé le champ vers un local et que je l’ai modifié dans le test, ça a fonctionné correctement. Cependant, si j’essayais un deuxième test, ce n’était pas le simulacre de mon test mais le simulacre du premier test et j’ai à nouveau obtenu les interactions zéro.

Quand je mets la création du simulacre dans le @BeforeClass, le logger dans l’object à tester est toujours le simulacre mais voyez la note ci-dessous pour les problèmes avec ceci …

Classe sous test

 import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MyClassWithSomeLogging { private static final Logger LOG = LoggerFactory.getLogger(MyClassWithSomeLogging.class); public void doStuff(boolean b) { if(b) { LOG.info("true"); } else { LOG.info("false"); } } } 

Tester

 import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.mockito.Mockito.*; import static org.powermock.api.mockito.PowerMockito.mock; import static org.powermock.api.mockito.PowerMockito.*; import static org.powermock.api.mockito.PowerMockito.when; @RunWith(PowerMockRunner.class) @PrepareForTest({LoggerFactory.class}) public class MyClassWithSomeLoggingTest { private static Logger mockLOG; @BeforeClass public static void setup() { mockStatic(LoggerFactory.class); mockLOG = mock(Logger.class); when(LoggerFactory.getLogger(any(Class.class))).thenReturn(mockLOG); } @Test public void testIt() { MyClassWithSomeLogging myClassWithSomeLogging = new MyClassWithSomeLogging(); myClassWithSomeLogging.doStuff(true); verify(mockLOG, times(1)).info("true"); } @Test public void testIt2() { MyClassWithSomeLogging myClassWithSomeLogging = new MyClassWithSomeLogging(); myClassWithSomeLogging.doStuff(false); verify(mockLOG, times(1)).info("false"); } @AfterClass public static void verifyStatic() { verify(mockLOG, times(1)).info("true"); verify(mockLOG, times(1)).info("false"); verify(mockLOG, times(2)).info(anySsortingng()); } } 

Remarque

Si vous avez deux tests avec la même attente, je devais faire la vérification dans le @AfterClass lorsque les invocations sur le statique étaient empilées – verify(mockLOG, times(2)).info("true"); – plutôt que des temps (1) dans chaque test car le deuxième test échouerait en indiquant là où 2 l’invoque. C’est un joli pantalon mais je n’ai pas pu trouver un moyen d’effacer les invocations. J’aimerais savoir si quelqu’un peut trouver une solution à ce problème ….

En réponse à votre première question, cela devrait être aussi simple que de remplacer:

  when(LoggerFactory.getLogger(GoodbyeController.class)).thenReturn(loggerMock); 

avec

  when(LoggerFactory.getLogger(any(Class.class))).thenReturn(loggerMock); 

En ce qui concerne votre deuxième question (et peut-être le comportement déroutant avec le premier), je pense que le problème est que l’enregistreur est statique. Alors,

 private static Logger logger = LoggerFactory.getLogger(GoodbyeController.class); 

est exécuté lorsque la classe est initialisée, et non lorsque l’ object est instancié. Parfois, cela peut être à peu près au même moment, alors ça va, mais c’est difficile à garantir. Vous avez donc configuré LoggerFactory.getLogger pour renvoyer votre maquette, mais la variable de journalisation a peut-être déjà été définie avec un véritable object Logger au moment de la configuration de vos simulations.

Vous pourrez peut-être définir explicitement l’enregistreur en utilisant quelque chose comme ReflectionTestUtils (je ne sais pas si cela fonctionne avec des champs statiques) ou le remplacer par un champ statique en un champ d’instance. Dans tous les cas, vous n’avez pas besoin de vous moquer de LoggerFactory.getLogger car vous injecterez directement l’instance de Logger simulée.

Je pense que vous pouvez réinitialiser les invocations en utilisant Mockito.reset (mockLog). Vous devriez appeler cela avant chaque test, donc à l’intérieur de @Before serait un bon endroit.

Utilisez une injection explicite. Aucune autre approche ne vous permettra par exemple d’exécuter des tests en parallèle dans la même JVM.

Les patterns qui utilisent un classloader large comme le classeur de journaux statique ou le traitement des problèmes d’environnement, comme logback.XML, sont un problème en matière de test.

Considérez les tests parallèles que je mentionne ou considérez le cas où vous voulez intercepter la journalisation du composant A dont la construction est cachée derrière api B. Ce dernier cas est facile à gérer si vous utilisez une loggerfactory injectée de dépendance depuis le haut, mais pas si vous injectez Logger car il n’y a pas de couture dans cet assemblage chez ILoggerFactory.getLogger.

Et ce n’est pas tout sur les tests unitaires non plus. Parfois, nous souhaitons que les tests d’intégration émettent une journalisation. Parfois, nous ne le faisons pas. Quelqu’un veut que certains des tests d’intégration soient supprimés de manière sélective, par exemple pour les erreurs attendues qui risqueraient de perturber la console CI et de créer la confusion. Tout est facile si vous injectez ILoggerFactory en haut de votre ligne principale (ou quel que soit le framework que vous utilisez)

Alors…

Soit injecter un protractor comme suggéré ou adopter un modèle d’injection de ILoggerFactory. Par injection ILoggerFactory explicite plutôt que Logger, vous pouvez prendre en charge de nombreux schémas d’access / interception et la parallélisation.