Following a recent article on how to Test System.out with JUnit, here’s a follow up on how to test log4J with JUnit. This article describes a technique to test log4J output in a JUnit test by adding a custom appender. This allows us to verify that log4j output contains expected Strings.
Test log4j with a custom appender
Log4J outputs messages to all configured appenders. In a typical system, we may output to the console and / or one or more files. Usually this is configured in the log4j.properties or log4j.xml configuration file. It’s also possible to add appenders programmatically. We can verify output by adding an appender that writes to a sink that we can read from. This should be set up before every test.
private static final String APPENDER_NAME = "log4jRuleAppender"; private static final Layout LAYOUT = new SimpleLayout(); private Logger logger; private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); @Before public void setupAppender() { logger = Logger.getLogger(ClassUnderTest.class) Appender appender = new WriterAppender(LAYOUT, outContent); appender.setName(APPENDER_NAME); logger.addAppender(appender); }
To view contents of the log, inspect the ByteArrayOutputStream with outContent.toString(). A typical test looks like this:
@Test public doAThing_logsStuff() throws Exception { ClassUnderTest.doAThing(); assertThat(outContent.toString(), containsString("expected output")); }
Using a JUnit @Rule
We can create a JUnit @Rule to extract this work from the test class and make it more reusable. The rule class looks like this:
import org.apache.log4j.Appender; import org.apache.log4j.Layout; import org.apache.log4j.Logger; import org.apache.log4j.SimpleLayout; import org.apache.log4j.WriterAppender; import org.junit.rules.ExternalResource; import java.io.ByteArrayOutputStream; public class LogAppenderResource extends ExternalResource { private static final String APPENDER_NAME = "log4jRuleAppender"; private static final Layout LAYOUT = new SimpleLayout(); private Logger logger; private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); public LogAppenderResource(Logger logger) { this.logger = logger; } @Override protected void before() { Appender appender = new WriterAppender(LAYOUT, outContent); appender.setName(APPENDER_NAME); logger.addAppender(appender); } @Override protected void after() { logger.removeAppender(APPENDER_NAME); } public String getOutput() { return outContent.toString(); } }
The @Rule can be used in a test class like this:
public class LoggingTextListenerTest { @Rule public LogAppenderResource appender = new LogAppenderResource(Logger.getLogger(LoggingTextListener.class)); private LoggingTextListener listener = new LoggingTextListener(); // Class under test @Test public void startedEvent_isLogged() { listener.started(); assertThat(appender.getOutput(), containsString("started")); } }
This example was taken from the VoiceXMLRiot project in GitHub. The @Rule is LogAppenderResource and an example of its use is LoggingTextListenerTest.
[…] see also the follow up article on how to Test log4j with JUnit if you’re interested in specifically testing log […]
I know this is an old article but one thing, in “Test log4j with a custom appender ” you should also call removeAppender in an @After so the appender list doesn’t keep growing. It seems harmless but it’s bad practice to add without a corresponding remove.