Comment faire une déclaration JUnit sur un message dans un enregistreur

J’ai un code sous test qui appelle un enregistreur Java pour signaler son statut. Dans le code de test JUnit, je voudrais vérifier que l’entrée de journal correcte a été effectuée dans cet enregistreur. Quelque chose dans les lignes suivantes:

methodUnderTest(bool x){ if(x) logger.info("x happened") } @Test tester(){ // perhaps setup a logger first. methodUnderTest(true); assertXXXXXX(loggedLevel(),Level.INFO); } 

Je suppose que cela pourrait être fait avec un enregistreur (ou un gestionnaire, ou un formateur) spécialement adapté, mais je préférerais réutiliser une solution qui existe déjà. (Et pour être honnête, je ne vois pas comment accéder à l’enregistrement de journal à partir d’un enregistreur, mais supposons que c’est possible.)

J’ai aussi eu besoin de ça plusieurs fois. J’ai rassemblé un petit échantillon ci-dessous, que vous souhaitez adapter à vos besoins. Fondamentalement, vous créez votre propre Appender et l’ajoutez à l’enregistreur souhaité. Si vous souhaitez tout rassembler, l’enregistreur racine est un bon sharepoint départ, mais vous pouvez utiliser un outil plus spécifique si vous le souhaitez. N’oubliez pas de supprimer l’Appender lorsque vous avez terminé, sinon vous risquez de créer une fuite de mémoire. Ci-dessous, je l’ai fait dans le test, mais setUp ou @Before et tearDown ou @After pourraient être de meilleurs endroits, selon vos besoins.

En outre, l’implémentation ci-dessous rassemble tout dans une List en mémoire. Si vous vous connectez beaucoup, vous pouvez envisager d’append un filtre pour supprimer des entrées ennuyeuses ou pour écrire le journal dans un fichier temporaire sur le disque (Astuce: LoggingEvent est Serializable , vous devriez donc pouvoir sérialiser les objects de l’événement, si votre message de journal est.)

 import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.spi.LoggingEvent; import org.junit.Test; import java.util.ArrayList; import java.util.List; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; public class MyTest { @Test public void test() { final TestAppender appender = new TestAppender(); final Logger logger = Logger.getRootLogger(); logger.addAppender(appender); try { Logger.getLogger(MyTest.class).info("Test"); } finally { logger.removeAppender(appender); } final List log = appender.getLog(); final LoggingEvent firstLogEntry = log.get(0); assertThat(firstLogEntry.getLevel(), is(Level.INFO)); assertThat((Ssortingng) firstLogEntry.getMessage(), is("Test")); assertThat(firstLogEntry.getLoggerName(), is("MyTest")); } } class TestAppender extends AppenderSkeleton { private final List log = new ArrayList(); @Override public boolean requiresLayout() { return false; } @Override protected void append(final LoggingEvent loggingEvent) { log.add(loggingEvent); } @Override public void close() { } public List getLog() { return new ArrayList(log); } } 

Merci beaucoup pour ces réponses (étonnamment) rapides et utiles; ils m’ont mis sur la bonne voie pour ma solution.

La base de code que je veux utiliser, utilise java.util.logging comme mécanisme de consignation, et je ne me sens pas suffisamment à l’aise dans ces codes pour changer complètement cela en log4j ou en interfaces / façades de consignateur. Mais sur la base de ces suggestions, j’ai «piraté» une extension julhandler et cela fonctionne comme un régal.

Un bref résumé suit. Étendez java.util.logging.Handler :

 class LogHandler extends Handler { Level lastLevel = Level.FINEST; public Level checkLevel() { return lastLevel; } public void publish(LogRecord record) { lastLevel = record.getLevel(); } public void close(){} public void flush(){} } 

De toute évidence, vous pouvez stocker autant que vous voulez / voulez / avez besoin de LogRecord , ou les mettre tous dans une stack jusqu’à ce que vous obteniez un débordement.

Dans la préparation du junit-test, vous créez un java.util.logging.Logger et lui ajoutez un nouveau LogHandler :

 @Test tester() { Logger logger = Logger.getLogger("my junit-test logger"); LogHandler handler = new LogHandler(); handler.setLevel(Level.ALL); logger.setUseParentHandlers(false); logger.addHandler(handler); logger.setLevel(Level.ALL); 

L’appel à setUseParentHandlers() consiste à faire taire les gestionnaires normaux, de sorte que (pour ce test de Junit, aucune journalisation inutile ne se produise). Faites ce que votre code sous test a besoin pour utiliser ce logger, exécutez le test et assertEquality:

  libraryUnderTest.setLogger(logger); methodUnderTest(true); // see original question. assertEquals("Log level as expected?", Level.INFO, handler.checkLevel() ); } 

(Bien sûr, vous déplacerez une grande partie de ce travail dans une méthode @Before et apporterez diverses autres améliorations, mais cela encombrerait cette présentation.)

Effectivement, vous testez un effet secondaire d’une classe dépendante. Pour les tests unitaires, il vous suffit de vérifier que

logger.info()

a été appelé avec le paramètre correct. Par conséquent, utilisez un cadre moqueur pour émuler un enregistreur et cela vous permettra de tester le comportement de votre propre classe.

Le moquage est une option ici, bien que ce soit difficile, car les enregistreurs sont généralement des systèmes statiques finaux privés – par conséquent, configurer un enregistreur de simulation ne serait pas une mince affaire ou nécessiterait une modification de la classe testée.

Vous pouvez créer un Appender personnalisé (ou ce qu’il appelle) et l’enregistrer – soit via un fichier de configuration test uniquement, soit à l’exécution (d’une certaine manière, en fonction de la structure de journalisation). Et puis vous pouvez obtenir cet appender (soit de manière statique, si elle est déclarée dans le fichier de configuration, soit par sa référence actuelle, si vous le twigz à l’exécution), et vérifiez son contenu.

Une autre option consiste à simuler Appender et à vérifier si le message a été consigné sur ce composant. Exemple pour Log4j 1.2.x et mockito:

 import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import org.apache.log4j.Appender; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.spi.LoggingEvent; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; public class MyTest { private final Appender appender = mock(Appender.class); private final Logger logger = Logger.getRootLogger(); @Before public void setup() { logger.addAppender(appender); } @Test public void test() { // when Logger.getLogger(MyTest.class).info("Test"); // then ArgumentCaptor argument = ArgumentCaptor.forClass(LoggingEvent.class); verify(appender).doAppend(argument.capture()); assertEquals(Level.INFO, argument.getValue().getLevel()); assertEquals("Test", argument.getValue().getMessage()); assertEquals("MyTest", argument.getValue().getLoggerName()); } @After public void cleanup() { logger.removeAppender(appender); } } 

Voici ce que j’ai fait pour la logback.

J’ai créé une classe TestAppender:

 public class TestAppender extends AppenderBase { private Stack events = new Stack(); @Override protected void append(ILoggingEvent event) { events.add(event); } public void clear() { events.clear(); } public ILoggingEvent getLastEvent() { return events.pop(); } } 

Ensuite, dans le parent de ma classe de test, j’ai créé une méthode:

 protected TestAppender testAppender; @BeforeClass public void setupLogsForTesting() { Logger root = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); testAppender = (TestAppender)root.getAppender("TEST"); if (testAppender != null) { testAppender.clear(); } } 

J’ai un fichier logback-test.xml défini dans src / test / resources et j’ai ajouté un appender de test:

   %m%n   

et ajouté cet appender à l’appendeur racine:

      

Maintenant, dans mes classes de test qui s’étendent de ma classe de test parent, je peux obtenir l’appender et obtenir le dernier message enregistré et vérifier le message, le niveau, le jetable.

 ILoggingEvent lastEvent = testAppender.getLastEvent(); assertEquals(lastEvent.getMessage(), "..."); assertEquals(lastEvent.getLevel(), Level.WARN); assertEquals(lastEvent.getThrowableProxy().getMessage(), "..."); 

Inspiré par la solution de @ RonaldBlaschke, j’ai trouvé ceci:

 public class Log4JTester extends ExternalResource { TestAppender appender; @Override protected void before() { appender = new TestAppender(); final Logger rootLogger = Logger.getRootLogger(); rootLogger.addAppender(appender); } @Override protected void after() { final Logger rootLogger = Logger.getRootLogger(); rootLogger.removeAppender(appender); } public void assertLogged(Matcher matcher) { for(LoggingEvent event : appender.events) { if(matcher.matches(event.getMessage())) { return; } } fail("No event matches " + matcher); } private static class TestAppender extends AppenderSkeleton { List events = new ArrayList(); @Override protected void append(LoggingEvent event) { events.add(event); } @Override public void close() { } @Override public boolean requiresLayout() { return false; } } } 

… ce qui vous permet de faire:

 @Rule public Log4JTester logTest = new Log4JTester(); @Test public void testFoo() { user.setStatus(Status.PREMIUM); logTest.assertLogged( ssortingngContains("Note added to account: premium customer")); } 

Vous pourriez probablement le faire utiliser le hamcrest de manière plus intelligente, mais je l’ai laissé à cela.

Comme mentionné des autres, vous pouvez utiliser un cadre moqueur. Pour que cela fonctionne, vous devez exposer le logger dans votre classe (bien que je préfère sans doute le rendre privé au lieu de créer un setter public).

L’autre solution consiste à créer un faux enregistreur à la main. Vous devez écrire le faux enregistreur (plus de code), mais dans ce cas, je préférerais une meilleure lisibilité des tests par rapport au code enregistré dans le cadre moqueur.

Je ferais quelque chose comme ça:

 class FakeLogger implements ILogger { public List infos = new ArrayList(); public List errors = new ArrayList(); public void info(Ssortingng message) { infos.add(message); } public void error(Ssortingng message) { errors.add(message); } } class TestMyClass { private MyClass myClass; private FakeLogger logger; @Before public void setUp() throws Exception { myClass = new MyClass(); logger = new FakeLogger(); myClass.logger = logger; } @Test public void testMyMethod() { myClass.myMethod(true); assertEquals(1, logger.infos.size()); } } 

En ce qui me concerne, vous pouvez simplifier votre test en utilisant JUnit avec Mockito . Je propose la solution suivante pour cela:

 import org.apache.log4j.Appender; import org.apache.log4j.Level; import org.apache.log4j.LogManager; import org.apache.log4j.spi.LoggingEvent; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; import static org.mockito.Mockito.times; @RunWith(MockitoJUnitRunner.class) public class MyLogTest { private static final Ssortingng FIRST_MESSAGE = "First message"; private static final Ssortingng SECOND_MESSAGE = "Second message"; @Mock private Appender appender; @Captor private ArgumentCaptor captor; @InjectMocks private MyLog; @Before public void setUp() { LogManager.getRootLogger().addAppender(appender); } @After public void tearDown() { LogManager.getRootLogger().removeAppender(appender); } @Test public void shouldLogExactlyTwoMessages() { testedClass.foo(); then(appender).should(times(2)).doAppend(captor.capture()); List loggingEvents = captor.getAllValues(); assertThat(loggingEvents).extracting("level", "renderedMessage").containsExactly( tuple(Level.INFO, FIRST_MESSAGE) tuple(Level.INFO, SECOND_MESSAGE) ); } } 

C’est pourquoi nous avons une grande flexibilité pour les tests avec différentes quantités de messages.

Pour log4j2, la solution est légèrement différente car AppenderSkeleton n’est plus disponible. En outre, l’utilisation de Mockito ou d’une bibliothèque similaire pour créer un Appender avec un ArgumentCaptor ne fonctionnera pas si vous attendez plusieurs messages de journalisation, car MutableLogEvent est réutilisé sur plusieurs messages de journal. La meilleure solution que j’ai trouvée pour log4j2 est la suivante:

 private static MockedAppender mockedAppender; private static Logger logger; @Before public void setup() { mockedAppender.message.clear(); } /** * For some reason mvn test will not work if this is @Before, but in eclipse it works! As a * result, we use @BeforeClass. */ @BeforeClass public static void setupClass() { mockedAppender = new MockedAppender(); logger = (Logger)LogManager.getLogger(MatchingMesortingcsLogger.class); logger.addAppender(mockedAppender); logger.setLevel(Level.INFO); } @AfterClass public static void teardown() { logger.removeAppender(mockedAppender); } @Test public void test() { // do something that causes logs for (Ssortingng e : mockedAppender.message) { // add asserts for the log messages } } private static class MockedAppender extends AbstractAppender { List message = new ArrayList<>(); protected MockedAppender() { super("MockedAppender", null, null); } @Override public void append(LogEvent event) { message.add(event.getMessage().getFormattedMessage()); } } 

Sensationnel. Je ne sais pas pourquoi c’était si difficile. J’ai constaté que je ne pouvais utiliser aucun des exemples de code ci-dessus car j’utilisais log4j2 sur slf4j. Ceci est ma solution:

 public class SpecialLogServiceTest { @Mock private Appender appender; @Captor private ArgumentCaptor captor; @InjectMocks private SpecialLogService specialLogService; private LoggerConfig loggerConfig; @Before public void setUp() { // prepare the appender so Log4j likes it when(appender.getName()).thenReturn("MockAppender"); when(appender.isStarted()).thenReturn(true); when(appender.isStopped()).thenReturn(false); final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); final Configuration config = ctx.getConfiguration(); loggerConfig = config.getLoggerConfig("org.example.SpecialLogService"); loggerConfig.addAppender(appender, AuditLogCRUDService.LEVEL_AUDIT, null); } @After public void tearDown() { loggerConfig.removeAppender("MockAppender"); } @Test public void writeLog_shouldCreateCorrectLogMessage() throws Exception { SpecialLog specialLog = new SpecialLogBuilder().build(); Ssortingng expectedLog = "this is my log message"; specialLogService.writeLog(specialLog); verify(appender).append(captor.capture()); assertThat(captor.getAllValues().size(), is(1)); assertThat(captor.getAllValues().get(0).getMessage().toSsortingng(), is(expectedLog)); } } 

Une autre idée qui mérite d’être mentionnée, bien qu’il s’agisse d’un sujet plus ancien, est la création d’un producteur de CDI pour injecter votre enregistreur afin que le moquer devienne facile. (Et cela donne aussi l’avantage de ne plus avoir à déclarer la “déclaration entière du logger”, mais c’est hors sujet)

Exemple:

Créer le logger à injecter:

 public class CdiResources { @Produces @LoggerType public Logger createLogger(final InjectionPoint ip) { return Logger.getLogger(ip.getMember().getDeclaringClass()); } } 

Le qualificatif:

 @Qualifier @Retention(RetentionPolicy.RUNTIME) @Target({TYPE, METHOD, FIELD, PARAMETER}) public @interface LoggerType { } 

Utiliser l’enregistreur dans votre code de production:

 public class ProductionCode { @Inject @LoggerType private Logger logger; public void logSomething() { logger.info("something"); } } 

Tester le logger dans votre code de test (en donnant un exemple easyMock):

 @TestSubject private ProductionCode productionCode = new ProductionCode(); @Mock private Logger logger; @Test public void testTheLogger() { logger.info("something"); replayAll(); productionCode.logSomething(); } 

En utilisant Jmockit (1.21), j’ai pu écrire ce test simple. Le test vérifie qu’un message ERROR spécifique est appelé une seule fois.

 @Test public void testErrorMessage() { final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger( MyConfig.class ); new Expectations(logger) {{ //make sure this error is happens just once. logger.error( "Something went wrong..." ); times = 1; }}; new MyTestObject().runSomethingWrong( "aaa" ); //SUT that eventually cause the error in the log. } 

Se moquer de l’appendeur peut aider à capturer les lignes de journal. Rechercher un exemple sur: http://clearqa.blogspot.co.uk/2016/12/test-log-lines.html

 // Fully working test at: https://github.com/njaiswal/logLineTester/blob/master/src/test/java/com/nj/Utils/UtilsTest.java @Test public void testUtilsLog() throws InterruptedException { Logger utilsLogger = (Logger) LoggerFactory.getLogger("com.nj.utils"); final Appender mockAppender = mock(Appender.class); when(mockAppender.getName()).thenReturn("MOCK"); utilsLogger.addAppender(mockAppender); final List capturedLogs = Collections.synchronizedList(new ArrayList<>()); final CountDownLatch latch = new CountDownLatch(3); //Capture logs doAnswer((invocation) -> { LoggingEvent loggingEvent = invocation.getArgumentAt(0, LoggingEvent.class); capturedLogs.add(loggingEvent.getFormattedMessage()); latch.countDown(); return null; }).when(mockAppender).doAppend(any()); //Call method which will do logging to be tested Application.main(null); //Wait 5 seconds for latch to be true. That means 3 log lines were logged assertThat(latch.await(5L, TimeUnit.SECONDS), is(true)); //Now assert the captured logs assertThat(capturedLogs, hasItem(containsSsortingng("One"))); assertThat(capturedLogs, hasItem(containsSsortingng("Two"))); assertThat(capturedLogs, hasItem(containsSsortingng("Three"))); } 

Utilisez le code ci-dessous. J’utilise le même code pour mon test d’intégration de spring où j’utilise la journalisation pour la journalisation. Utilisez la méthode assertJobIsScheduled pour affirmer le texte imprimé dans le journal.

 import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.LoggingEvent; import ch.qos.logback.core.Appender; private Logger rootLogger; final Appender mockAppender = mock(Appender.class); @Before public void setUp() throws Exception { initMocks(this); when(mockAppender.getName()).thenReturn("MOCK"); rootLogger = (Logger) LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME); rootLogger.addAppender(mockAppender); } private void assertJobIsScheduled(final Ssortingng matcherText) { verify(mockAppender).doAppend(argThat(new ArgumentMatcher() { @Override public boolean matches(final Object argument) { return ((LoggingEvent)argument).getFormattedMessage().contains(matcherText); } })); } 

Si vous utilisez java.util.logging.Logger cet article peut être très utile, il crée un nouveau gestionnaire et effectue des assertions sur la sortie du journal: http://octodecillion.com/blog/jmockit-test-logging/

Il y a deux choses que vous pourriez essayer de tester.

  • Lorsqu’il y a un événement intéressant l’opérateur de mon programme, mon programme effectue-t-il une opération de journalisation appropriée, qui peut informer l’opérateur de cet événement.
  • Lorsque mon programme exécute une opération de journalisation, le message de journal produit at-il le bon texte?

Ces deux choses sont en fait des choses différentes et pourraient donc être testées séparément. Cependant, tester le second (le texte des messages) est tellement problématique que je déconseille de le faire du tout. Un test de texte de message consistera finalement à vérifier qu’une chaîne de texte (le texte du message attendu) est identique ou peut être dérivée de manière sortingviale de la chaîne de texte utilisée dans votre code de journalisation.

  • Ces tests ne testent pas du tout la logique du programme, ils testent uniquement qu’une ressource (une chaîne) est équivalente à une autre ressource.
  • Les tests sont fragiles même une modification mineure de la mise en forme d’un message de journal rompt vos tests.
  • Les tests sont incompatibles avec l’internationalisation (traduction) de votre interface de journalisation. Les tests supposent qu’il n’y a qu’un seul texte de message possible, et donc un seul langage humain possible.

Notez que le code de votre programme (implémentant une logique métier, peut-être) appelant directement l’interface de journalisation de texte est de mauvaise conception (mais malheureusement très commode). Le code responsable de la logique métier décide également de certaines règles de journalisation et du texte des messages de journalisation. Il associe la logique métier au code d’interface utilisateur (oui, les messages de journal font partie de l’interface utilisateur de votre programme). Ces choses devraient être séparées.

Je recommande donc que la logique métier ne génère pas directement le texte des messages de journal. Au lieu de cela, déléguez-le à un object de journalisation.

  • La classe de l’object de journalisation doit fournir une API interne appropriée que votre object métier peut utiliser pour exprimer l’événement survenu à l’aide d’objects de votre modèle de domaine, et non de chaînes de texte.
  • L’implémentation de votre classe de journalisation est chargée de produire des représentations textuelles de ces objects de domaine et de rendre une description textuelle appropriée de l’événement, puis de transférer ce message texte vers la structure de journalisation de bas niveau (JUL, log4j ou slf4j, par exemple).
  • Votre logique métier est uniquement responsable d’appeler les méthodes correctes de l’API interne de votre classe de consignateur, en transmettant les objects de domaine appropriés, pour décrire les événements réels qui se sont produits.
  • Votre classe de consignation concrète implements une interface qui décrit l’API interne que votre logique métier peut utiliser.
  • Votre ou vos classes qui implémentent la logique métier et doivent effectuer la journalisation font référence à l’object de journalisation à déléguer. La classe de la référence est l’ interface abstraite.
  • Utilisez l’dependency injection pour configurer la référence au consignateur.

Vous pouvez ensuite vérifier que vos classes de logique métier indiquent correctement les événements à l’interface de journalisation, en créant un enregistreur de simulation qui implémente l’API de journalisation interne et en utilisant l’dependency injection dans la phase de configuration de votre test.

Comme ça:

  public class MyService {// The class we want to test private final MyLogger logger; public MyService(MyLogger logger) { this.logger = Objects.requireNonNull(logger); } public void performTwiddleOperation(Foo foo) {// The method we want to test ...// The business logic logger.performedTwiddleOperation(foo); } }; public interface MyLogger { public void performedTwiddleOperation(Foo foo); ... }; public final class MySl4jLogger: implements MyLogger { ... @Override public void performedTwiddleOperation(Foo foo) { logger.info("twiddled foo " + foo.getId()); } } public final void MyProgram { public static void main(Ssortingng[] argv) { ... MyLogger logger = new MySl4jLogger(...); MyService service = new MyService(logger); startService(service);// or whatever you must do ... } } public class MyServiceTest { ... static final class MyMockLogger: implements MyLogger { private Food.id id; private int nCallsPerformedTwiddleOperation; ... @Override public void performedTwiddleOperation(Foo foo) { id = foo.id; ++nCallsPerformedTwiddleOperation; } void assertCalledPerformedTwiddleOperation(Foo.id id) { assertEquals("Called performedTwiddleOperation", 1, nCallsPerformedTwiddleOperation); assertEquals("Called performedTwiddleOperation with correct ID", id, this.id); } }; @Test public void testPerformTwiddleOperation_1() { // Setup MyMockLogger logger = new MyMockLogger(); MyService service = new MyService(logger); Foo.Id id = new Foo.Id(...); Foo foo = new Foo(id, 1); // Execute service.performedTwiddleOperation(foo); // Verify ... logger.assertCalledPerformedTwiddleOperation(id); } } 

Ce que j’ai fait si tout ce que je veux faire, c’est de voir qu’une chaîne a été enregistrée (au lieu de vérifier des instructions de journal exactes qui sont juste trop fragiles) est de redirect StdOut vers un tampon, faire une contient, puis réinitialiser StdOut:

 PrintStream original = System.out; ByteArrayOutputStream buffer = new ByteArrayOutputStream(); System.setOut(new PrintStream(buffer)); // Do something that logs assertTrue(buffer.toSsortingng().contains(myMessage)); System.setOut(original); 

L’API pour Log4J2 est légèrement différente. Vous pouvez également utiliser son appender asynchrone. J’ai créé un appender verrouillé pour cela:

  public static class LatchedAppender extends AbstractAppender implements AutoCloseable { private final List messages = new ArrayList<>(); private final CountDownLatch latch; private final LoggerConfig loggerConfig; public LatchedAppender(Class< ?> classThatLogs, int expectedMessages) { this(classThatLogs, null, null, expectedMessages); } public LatchedAppender(Class< ?> classThatLogs, Filter filter, Layout< ? extends Serializable> layout, int expectedMessages) { super(classThatLogs.getName()+"."+"LatchedAppender", filter, layout); latch = new CountDownLatch(expectedMessages); final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); final Configuration config = ctx.getConfiguration(); loggerConfig = config.getLoggerConfig(LogManager.getLogger(classThatLogs).getName()); loggerConfig.addAppender(this, Level.ALL, ThresholdFilter.createFilter(Level.ALL, null, null)); start(); } @Override public void append(LogEvent event) { messages.add(event); latch.countDown(); } public List awaitMessages() throws InterruptedException { assertTrue(latch.await(10, TimeUnit.SECONDS)); return messages; } @Override public void close() { stop(); loggerConfig.removeAppender(this.getName()); } } 

Utilisez-le comme ceci:

  try (LatchedAppender appender = new LatchedAppender(ClassUnderTest.class, 1)) { ClassUnderTest.methodThatLogs(); List events = appender.awaitMessages(); assertEquals(1, events.size()); //more assertions here }//appender removed 

Voici une solution de consignation simple et efficace.
Il ne nécessite pas d’append / créer une nouvelle classe.
Il s’appuie sur ListAppender : un appendeur de consignation Whitebox dans lequel des entrées de journal sont ajoutées dans un champ public List que nous pourrions utiliser pour faire nos assertions.

Voici un exemple simple.

Classe Foo:

 import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Foo { static final Logger LOGGER = LoggerFactory.getLogger(Foo .class); public void doThat() { logger.info("start"); //... logger.info("finish"); } } 

Classe FooTest:

 import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; public class FooTest { @Test void doThat() throws Exception { // get Logback Logger Logger fooLogger = (Logger) LoggerFactory.getLogger(Foo.class); // create and start a ListAppender ListAppender listAppender = new ListAppender<>(); listAppender.start(); // add the appender to the logger fooLogger.addAppender(listAppender); // call method under test Foo foo = new Foo(); foo.doThat(); // JUnit assertions List logsList = listAppender.list; assertEquals("start", logsList.get(0) .getMessage()); assertEquals(Level.INFO, logsList.get(0) .getLevel()); assertEquals("finish", logsList.get(1) .getMessage()); assertEquals(Level.INFO, logsList.get(1) .getLevel()); } } 

Les assertions JUnit ne semblent pas très adaptées pour affirmer certaines propriétés spécifiques des éléments de la liste.
Les librairies d’assignation / d’assertion comme AssertJ ou Hamcrest semblent mieux pour cela:

Avec AssertJ ce serait:

 import org.assertj.core.api.Assertions; Assertions.assertThat(listAppender.list) .extracting(ILoggingEvent::getMessage, ILoggingEvent::getLevel) .containsExactly(Tuple.tuple("start", Level.INFO), Tuple.tuple("finish", Level.INFO));