Last active
July 1, 2020 14:27
-
-
Save davidkuster/8bef03ab0b873f7b06b6796e1589b51a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tbd; | |
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; | |
import org.slf4j.LoggerFactory; | |
import java.util.Arrays; | |
import java.util.List; | |
import java.util.function.Consumer; | |
/** | |
* Utility to enable verification of log messages in tests. | |
* | |
* Usage is as follows: | |
* | |
* //@Test | |
* void shouldWriteLogMessage() { | |
* new TestLogger().with(testLogger -> { | |
* new ClassUnderTest.someCodeThatIncludesLogStatements(); | |
* | |
* testLogger.contains(Level.INFO, "Log message says: {}", "param to log message"); | |
* }); | |
* } | |
*/ | |
public class TestLogger { | |
private final Logger logger; | |
private final ListAppender<ILoggingEvent> listAppender; | |
public TestLogger() { | |
this.logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); | |
this.listAppender = new ListAppender<>(); | |
this.listAppender.start(); | |
this.logger.addAppender(this.listAppender); | |
} | |
/** | |
* This method wraps the given function with a Logback ListAppender, and provides a | |
* reference to that list appender to the calling function. Log messages can then be | |
* validated using the provided list appender reference. | |
* | |
* Example: | |
* | |
* new TestLogger().with(testLogger -> { | |
* new ClassUnderTest().someCodeThatIncludesLogStatements(); | |
* | |
* assertTrue(testLogger.contains(Level.DEBUG, "expected log message")); | |
* }); | |
* | |
* NOTE: | |
* | |
* The following example will not work. The test logging needs to be in place | |
* before the loggers in the class under test are initialized. | |
* | |
* ClassUnderTest cut = new ClassUnderTest(); | |
* new TestLogger().with(testLogger -> { | |
* cut.someCodeThatIncludesLogStatements(); | |
* | |
* assertTrue(testLogger.contains(Level.DEBUG, "expected log message")); | |
* }); | |
*/ | |
public void with(Consumer<TestLogger> function) { | |
try { | |
function.accept(this); | |
} finally { | |
logger.detachAppender(listAppender); | |
} | |
} | |
/** | |
* Determines whether a message was logged at the given level, with the given string, | |
* and the given parameters. | |
*/ | |
public boolean contains(Level level, String message, Object... params) { | |
return listAppender | |
.list | |
.stream() | |
.anyMatch(event -> { | |
// uncomment for debugging | |
//System.out.println( | |
// "message = " + event.getMessage() | |
// + ", args = " + Arrays.deepToString(event.getArgumentArray()) | |
// + ", params = " + Arrays.deepToString(params)); | |
return level.equals(event.getLevel()) | |
&& message.equals(event.getMessage()) | |
// arg array defaults to null and params defaults to empty array | |
&& ((event.getArgumentArray() == null && params.length == 0) | |
|| Arrays.equals(params, event.getArgumentArray())); | |
}); | |
} | |
// TODO: determine if it's worth validating the class of exception logged also. | |
// Note that it's not possible to have another method param after the Object... varargs above, | |
// so parameter order to a separate containsWithException() [or whatever] method would have | |
// to be different than the existing contains() method. | |
/** | |
* Returns the list of captured ILoggingEvents, for debugging or deeper verification | |
* than provided by the contains() method above. | |
*/ | |
public List<ILoggingEvent> getLogEvents() { | |
return listAppender.list; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment