Unit Test Log4j2 Log Output
Sometimes you want to test if certain log output gets generated when certain events happen in your application. Here is how I unit test that using log4j2 (version 2.11.0).
Use LoggerContextRule
to get to your ListAppender
quickly
If you are using JUnit 4, then the quickest solution would be one that is used by log4j2 itself:
import org.apache.logging.log4j.junit.LoggerContextRule;
/* other imports */
public class LogEventTest {
private static ListAppender appender;
@ClassRule
public static LoggerContextRule init = new LoggerContextRule("log4j2-test.yaml");
@BeforeClass
public static void setupLogging() {
appender = init.getListAppender("List");
}
@Before
public void clearAppender() {
appender.clear();
}
@Test
public void someMethodShouldLogAnError() {
// setup test and invoke logic
List<LogEvent> logEvents = appender.getEvents();
List<String> errors = logEvents.stream()
.filter(event -> event.getLevel().equals(Level.ERROR))
.map(event -> event.getMessage().getFormattedMessage())
.collect(Collectors.toList());
// we logged at least one event of level error
assertThat(errors.size(), is(greaterThanOrEqualTo(1)));
// log event message should contain "wrong" for example
assertThat(errors, everyItem(containsString("wrong")));
}
}
The LoggerContextRule
provides methods that come in handy while
testing. Here we use the getListAppender(...)
method to get access to
an appender that uses a list to store all log events. Before each test,
we clear the list, so we have a clean slate for new log events. The test
invokes the code-under-test, requests the log events from the appender
and filters them so that we only have the error log events left. Then we
that at least one error log message was captured and that it contains
the word “wrong”.
Use the LoggerContext
Instead of using the class rule (which lets you conveniently pass it the
file name of the configuration), you could also use the LoggerContext
:
@BeforeClass
public static void setupLogging() {
LoggerContext context = LoggerContext.getContext(false);
Logger logger = context.getLogger("com.relentlesscoding");
appender = (ListAppender) logger.getAppenders().get("List");
}
This might be your only option if you are working with JUnit 5 (and
eventually you will want to migrate to that). In JUnit 5 we can’t use
LoggerContextRule
anymore, because @Rule
s don’t longer exist (they
were replaced with an extension mechanism that works differently and
log4j2 doesn’t provide such an extension currently).
Create a working configuration
To get the examples working, we need to define an appender called “List” and a logger in our log4j2 configuration.
log4j2-test.yaml
Configuration:
status: warn
name: TestConfig
appenders:
Console:
name: STDOUT
List:
name: List
Loggers:
logger:
-
name: com.relentlesscoding
AppenderRef:
ref: List
Root:
level: info
AppenderRef:
ref: STDOUT
This configuration will send log events occurring in the package
com.relentlesscoding
and sub-packages to the appender with the name
“List” (which is of type ListAppender
). This configuration is
defined in YAML, but you can use XML, JSON or the properties format as
well.
Maven dependencies
To get the LoggerContextRule
in JUnit 4 working, you need the
following:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.0</version>
<type>test-jar</type>
</dependency>
To get the YAML log4j2 configuration working, you need:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.9.5</version>
</dependency>