Test log4j with JUnit using a custom appender

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.

Leave a Reply

Your email address will not be published. Required fields are marked *