4

I'm writing a test and I want to capture the messages sent on STDOUT by the tested method. Here is my code:

@Test
public void testAction() throws IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException,
        CmdLineException, IOException {
    PrintStream originalSTDOUT = System.out;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    PrintStream ps = new PrintStream(baos);

    try {
        System.setOut(ps);

        // Call to the method that will print text to STDOUT...

        ps.flush();
        String batchLog = baos.toString("UTF-8");
        assertTrue("Invalid exception text !", batchLog.contains("my expected text..."));
    } finally {
        System.setOut(originalSTDOUT);//Restore original STDOUT
        originalSTDOUT.write(baos.toByteArray());// Write back to STDOUT, even if I comment this line, outputs go to STDOUT... 

        ps.close();
    }
}

Unfortunately, batchLog is always empty although I can still read the expected text to STDOUT.

The method that will print text to STDOUT in fact catch an exception. It then passes it to a Logger like below:

log.warn("An error has occured:", e);

where e is the exception raised in the method I call in my test.

The Logger uses this appender for printing its messages: org.apache.log4j.ConsoleAppender. No special configuration is applied to this appender.

What am I missing ?

P.S.: This SO answer is interesting but it doesn't work for me since the ConsoleAppender is already created before the StandardOutputStreamLog rule can act.

Java 6
Junit 4.10

Community
  • 1
  • 1
Stephan
  • 41,764
  • 65
  • 238
  • 329
  • How does the method print text? `System.setOut` only changes the value of the `System.out` field; it may use a cached value of the field or use some other means of writing to stdout. – Joni Aug 12 '13 at 11:28
  • 1
    The code works fine as a standalone main method. Check your JUnit setup and teardown methods. Problem's somewhere else. – Ravi K Thapliyal Aug 12 '13 at 11:31
  • Can't reproduce this with a normal System.out.println(...), how u print text to stdout? – d0x Aug 12 '13 at 11:32
  • @Joni I have updated my post by adding how the method prints text to stdout. – Stephan Aug 12 '13 at 12:31
  • 2
    Since you're using Log4J can't you just change the logging configuration? The `ConsoleAppender` class uses whatever was the value of `System.out` when the appender was created; by the time your test executes it's too late to change it. See the source: http://www.docjar.com/html/api/org/apache/log4j/ConsoleAppender.java.html – Joni Aug 12 '13 at 12:36

3 Answers3

10

I finally solved my problem !

My first attempt was wrong because I redirect the STDOUT too late as @Joni stated in his comments: ConsoleAppender is already created.

Since, the exception message is handled by a Logger, I decided to add a WriterAppender backed by a StringWriter to it in order to get the message.

Here is my working solution:

@Test
public void testAction() throws IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException,
        CmdLineException, IOException {

        // Setup WriterAppender
        Writer w = new StringWriter();
        Layout l = new PatternLayout("%m%n");

        WriterAppender wa = new WriterAppender(l, w);
        wa.setEncoding("UTF-8");
        wa.setThreshold(Level.ALL);
        wa.activateOptions();// WriterAppender does nothing here, but I like defensive code...

        // Add it to logger
        Logger log = Logger.getLogger(ExceptionHandler.class);// ExceptionHandler is the class that contains this code : `log.warn("An error has occured:", e);'
        log.addAppender(wa);

        try {
             // Call to the method that will print text to STDOUT...

             String batchLog = w.toString();
             assertTrue("Invalid exception text !", batchLog.contains("my expected text..."));
        } finally {
             // Cleanup everything...
             log.removeAppender(wa);
             wa.close();
        }
}
Stephan
  • 41,764
  • 65
  • 238
  • 329
4

Slightly off topic, but in case some people (like me, when I first found this thread) might be interested in capturing log output via SLF4J, commons-testing's JUnit @Rule might help:

public class FooTest {
    @Rule
    public final ExpectedLogs logs = new ExpectedLogs() {{
        captureFor(Foo.class, LogLevel.WARN);
    }};

    @Test
    public void barShouldLogWarning() {
        assertThat(logs.isEmpty(), is(true)); // Nothing captured yet.

        // Logic using the class you are capturing logs for:
        Foo foo = new Foo();
        assertThat(foo.bar(), is(not(nullValue())));

        // Assert content of the captured logs:
        assertThat(logs.isEmpty(), is(false));
        assertThat(logs.contains("Your warning message here"), is(true));
    }
}

Disclaimer:

  • I developed this library since I could not find any suitable solution for my own needs.
  • Only bindings for log4j, log4j2 and logback are available at the moment, but I am happy to add more.
Marc Carré
  • 1,446
  • 13
  • 19
0

Try enabling the autoflushing true on PrintStream:

PrintStream ps = new PrintStream(baos,true);
Juned Ahsan
  • 67,789
  • 12
  • 98
  • 136