Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best way to unit-test SLF4J log messages?

I'm using slf4j and I want to unit test my code to make sure that warn/error log messages are generated under certain conditions. I'd rather these be strict unit tests, so I'd prefer not to have to pull up logging configuration from a file in order to test that the log messages are generated. The mocking framework I'm using is Mockito.

like image 636
Javid Jamae Avatar asked Jan 10 '11 18:01

Javid Jamae


People also ask

Should you unit test log messages?

Yes, we should test logging when the logging is doing something that is required. For example, you have hooks in some external application that scans the log for certain events. In that case you certainly want to ensure the logging is done.

How do I test a logger message in Junit?

@StefanoL: If a logger is defined with private static final Logger LOGGER = Logger. getLogger(someString); , you can still access it from unit tests with Logger. getLogger(someString); , modify it and add handlers (as in the accepted answer).

What logger does slf4j use?

SLF4J supports popular logging frameworks, namely log4j, java. util. logging, Simple logging and NOP.

What is the default logging level in slf4j?

slf4j. simpleLogger. defaultLogLevel - Default log level for all instances of SimpleLogger. Must be one of ("trace", "debug", "info", "warn", "error" or "off").


2 Answers

For testing slf4j without relying on a specific implementation (such as log4j), you can provide your own slf4j logging implementation as described in this SLF4J FAQ. Your implementation can record the messages that were logged and then be interrogated by your unit tests for validation.

The slf4j-test package does exactly this. It's an in-memory slf4j logging implementation that provides methods for retrieving logged messages.

like image 121
eakst7 Avatar answered Sep 22 '22 03:09

eakst7


Create a test rule:

    import ch.qos.logback.classic.Logger;     import ch.qos.logback.classic.spi.ILoggingEvent;     import ch.qos.logback.core.read.ListAppender;     import org.junit.rules.TestRule;     import org.junit.runner.Description;     import org.junit.runners.model.Statement;     import org.slf4j.LoggerFactory;          import java.util.List;     import java.util.stream.Collectors;          public class LoggerRule implements TestRule {            private final ListAppender<ILoggingEvent> listAppender = new ListAppender<>();       private final Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);            @Override       public Statement apply(Statement base, Description description) {         return new Statement() {           @Override           public void evaluate() throws Throwable {             setup();             base.evaluate();             teardown();           }         };       }            private void setup() {         logger.addAppender(listAppender);         listAppender.start();       }            private void teardown() {         listAppender.stop();         listAppender.list.clear();         logger.detachAppender(listAppender);       }            public List<String> getMessages() {         return listAppender.list.stream().map(e -> e.getMessage()).collect(Collectors.toList());       }            public List<String> getFormattedMessages() {         return listAppender.list.stream().map(e -> e.getFormattedMessage()).collect(Collectors.toList());       }          } 

Then use it:

    @Rule     public final LoggerRule loggerRule = new LoggerRule();          @Test     public void yourTest() {         // ...         assertThat(loggerRule.getFormattedMessages().size()).isEqualTo(2);     } 




----- JUnit 5 with Extension Oct 2021 -----

LogCapture:

public class LogCapture {    private ListAppender<ILoggingEvent> listAppender = new ListAppender<>();    LogCapture() {   }    public String getFirstFormattedMessage() {     return getFormattedMessageAt(0);   }    public String getLastFormattedMessage() {     return getFormattedMessageAt(listAppender.list.size() - 1);   }    public String getFormattedMessageAt(int index) {     return getLoggingEventAt(index).getFormattedMessage();   }    public LoggingEvent getLoggingEvent() {     return getLoggingEventAt(0);   }    public LoggingEvent getLoggingEventAt(int index) {     return (LoggingEvent) listAppender.list.get(index);   }    public List<LoggingEvent> getLoggingEvents() {     return listAppender.list.stream().map(e -> (LoggingEvent) e).collect(Collectors.toList());   }    public void setLogFilter(Level logLevel) {     listAppender.clearAllFilters();     listAppender.addFilter(buildLevelFilter(logLevel));   }    public void clear() {     listAppender.list.clear();   }    void start() {     setLogFilter(Level.INFO);     listAppender.start();   }    void stop() {     if (listAppender == null) {       return;     }      listAppender.stop();     listAppender.list.clear();     listAppender = null;   }    ListAppender<ILoggingEvent> getListAppender() {     return listAppender;   }    private Filter<ILoggingEvent> buildLevelFilter(Level logLevel) {     LevelFilter levelFilter = new LevelFilter();     levelFilter.setLevel(logLevel);     levelFilter.setOnMismatch(FilterReply.DENY);     levelFilter.start();      return levelFilter;   }  } 

LogCaptureExtension:

public class LogCaptureExtension implements ParameterResolver, AfterTestExecutionCallback {    private Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);    private LogCapture logCapture;    @Override   public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {     return parameterContext.getParameter().getType() == LogCapture.class;   }    @Override   public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {     logCapture = new LogCapture();      setup();      return logCapture;   }    @Override   public void afterTestExecution(ExtensionContext context) {     teardown();   }    private void setup() {     logger.addAppender(logCapture.getListAppender());     logCapture.start();   }    private void teardown() {     if (logCapture == null || logger == null) {       return;     }      logger.detachAndStopAllAppenders();     logCapture.stop();   }  } 

then use it:

@ExtendWith(LogCaptureExtension.class) public class SomeTest {    @Test   public void sometest(LogCapture logCapture)  {     // do test here      assertThat(logCapture.getLoggingEvents()).isEmpty();   }    // ... } 
like image 22
Andrew Feng Avatar answered Sep 23 '22 03:09

Andrew Feng