You don't want to see stack traces in unit tests. Unless something is wrong. That means you don't want to see stack traces when the test succeeds. But what if your code happily logs some stack traces?

Problem

Now, sometimes your test intentionally brings the test subject into a state where it logs an exception. For example the following unit test code:

javaMailSender.send(...some message...);
EasyMock.expectLastCall().andThrow(new MailSendException("booh"));
...do test...

And the following production code:

try {
    javaMailSender.send(mail);
}
catch (Exception exception) {
    log.info("exception while sending e-mail", exception);
}

As a result of this you will see an exception logged for every time your test runs (and succeeds):

2013-12-09 00:37:23,058 INFO [Mailer] [main] exception while sending e-mail
org.springframework.mail.MailSendException: booh
        at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:46)
        at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:85)
        at org.easymock.internal.ClassProxyFactory$MockMethodInterceptor.intercept(ClassProxyFactory.java:94)

If you're serious about your code, you just not want to see stack traces in your unit test's console output. I'm not talking about throwing exceptions, I'm talking about logging them. (You could argue that I should have thrown this exception, rather than logging it, but that's just not what the requirement was.)

Solution

In comes the LogMuter class below:

package com.totaalsoftware.common.test.log;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import org.apache.log4j.Appender;
import org.apache.log4j.Logger;
import org.apache.log4j.varia.NullAppender;

/**
 * Mute the log. Meant to be used during unit tests
 */
public class LogMuter {
    private List<Appender> savedAppenders = new ArrayList<Appender>();

    /**
     * Set up a new log muter.
     *
     * @return new log muter
     */
    public static LogMuter setupLogMuter() {
        LogMuter logMuter = new LogMuter();
        logMuter.disableOutput();
        return logMuter;
    }


    /**
     * Disable log output (mute the log).
     */
    public void disableOutput() {
        if (!savedAppenders.isEmpty()) {
            return;
        }
        Logger rootLogger = Logger.getRootLogger();
        @SuppressWarnings("unchecked")
        Enumeration<Appender> allAppenders = rootLogger.getAllAppenders();
        while (allAppenders.hasMoreElements()) {
            Appender appender = allAppenders.nextElement();
            savedAppenders.add(appender);
            rootLogger.removeAppender(appender);
        }
        rootLogger.removeAllAppenders();
        rootLogger.addAppender(new NullAppender());
    }

    /**
     * Enable log output (unmute the log).
     */
    public void enableOutput() {
        if (savedAppenders.isEmpty()) {
            return;
        }
        Logger rootLogger = Logger.getRootLogger();
        rootLogger.removeAllAppenders();
        for (Appender savedAppender : savedAppenders) {
            rootLogger.addAppender(savedAppender);
        }
        savedAppenders.clear();
    }

    /**
     * Tear down the log muter. Don't forget to tear down, else subsequent tests
     * that are executed will have their log muted too, since the logging
     * infrastructure is static
     */
    public void tearDown() {
        enableOutput();
    }

}

It can be conveniently used from within a unit test as follows:

javaMailSender.send(...some message...);
EasyMock.expectLastCall().andThrow(new MailSendException("booh"));
LogMuter logMuter = LogMuter.setupLogMuter();
...do test...
logMuter.tearDown();