JUnit test for System.out.println()
Asked Answered
F

15

448

I need to write JUnit tests for an old application that's poorly designed and is writing a lot of error messages to standard output. When the getResponse(String request) method behaves correctly it returns a XML response:

@BeforeClass
public static void setUpClass() throws Exception {
    Properties queries = loadPropertiesFile("requests.properties");
    Properties responses = loadPropertiesFile("responses.properties");
    instance = new ResponseGenerator(queries, responses);
}

@Test
public void testGetResponse() {
    String request = "<some>request</some>";
    String expResult = "<some>response</some>";
    String result = instance.getResponse(request);
    assertEquals(expResult, result);
}

But when it gets malformed XML or does not understand the request it returns null and writes some stuff to standard output.

Is there any way to assert console output in JUnit? To catch cases like:

System.out.println("match found: " + strExpr);
System.out.println("xml not well formed: " + e.getMessage());
Fluviomarine answered 13/7, 2009 at 13:18 Comment(1)
Related to, but not a duplicate of #3382301Dunstan
P
701

using ByteArrayOutputStream and System.setXXX is simple:

private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();
private final PrintStream originalOut = System.out;
private final PrintStream originalErr = System.err;

@Before
public void setUpStreams() {
    System.setOut(new PrintStream(outContent));
    System.setErr(new PrintStream(errContent));
}

@After
public void restoreStreams() {
    System.setOut(originalOut);
    System.setErr(originalErr);
}

sample test cases:

@Test
public void out() {
    System.out.print("hello");
    assertEquals("hello", outContent.toString());
}

@Test
public void err() {
    System.err.print("hello again");
    assertEquals("hello again", errContent.toString());
}

I used this code to test the command line option (asserting that -version outputs the version string, etc etc)

Edit: Prior versions of this answer called System.setOut(null) after the tests; This is the cause of NullPointerExceptions commenters refer to.

Pondweed answered 13/7, 2009 at 13:51 Comment(12)
Furthemore, I have used JUnitMatchers to test for responses: assertThat(result, containsString("<request:GetEmployeeByKeyResponse")); Thanks, dfa.Fluviomarine
I prefer to use System.setOut(null) to restore the stream back to what it was when the VM was launchedReformed
The javadocs don't say anything about being able to pass null to System.setOut or System.setErr. Are you sure this will work on all JREs?Currish
I encountered a NullPointerException in other tests after setting a null error stream as suggested above (in java.io.writer(Object), called internally by an XML validator). I would suggest instead saving the original in a field: oldStdErr = System.err and restoring this in the @After method.Antiperistalsis
I am having an issue with EclEmma with this solution. JUnit4 is working perfectly fine. But whenever I am calling outContent/errContent in a test, then the code coverage is wrongly performed and the branch is marked as "unexplored" while it obviously run through it during the test. I should add that I am also using the System Rules library. If anybody already encountered this problem and got a workaround I would be happy to know more about it.Nonsectarian
Great solution. Just a note for anyone using it, you may need to trim() whitespace/newline from outContent.Papyrology
This approach is fraught with problems because the standard output stream is a shared resource used by all parts of your program. It is better to use Dependency Injection to eliminate the direct use of the standard output stream: https://mcmap.net/q/80403/-junit-test-for-system-out-printlnDunstan
at the moment that seems to be the signle option to get it work with Junit 5 (doesn't have @Rules anymore)Brown
@BeforeAll and @AfterAll for JUnit5.Haughay
@Haughay you might want to use BeforeEach and AfterEach if only some of your tests use System.out.printlnSkippy
@Skippy wouldn't it work the same way as @BeforeAll and @AfterAll? if only some of the tests need that, wouldn't it be better to put the redirection in the specific tests body? then it wouldn't affect the other testsHaughay
@Haughay you are right, I must have skipped my coffee that morning.Skippy
M
108

I know this is an old thread, but there is a nice library to do this: System Rules
Example from the docs:

public void MyTest {
    @Rule
    public final SystemOutRule systemOutRule = new SystemOutRule().enableLog();

    @Test
    public void overrideProperty() {
        System.out.print("hello world");
        assertEquals("hello world", systemOutRule.getLog());
    }
}

It will also allow you to trap System.exit(-1) and other things that a command line tool would need to be tested for.

Moorhead answered 2/3, 2013 at 13:59 Comment(1)
This approach is fraught with problems because the standard output stream is a shared resource used by all parts of your program. It is better to use Dependency Injection to eliminate the direct use of the standard output stream: https://mcmap.net/q/80403/-junit-test-for-system-out-printlnDunstan
S
42

Instead of redirecting System.out, I would refactor the class that uses System.out.println() by passing a PrintStream as a collaborator and then using System.out in production and a Test Spy in the test. That is, use Dependency Injection to eliminate the direct use of the standard output stream.

In Production

ConsoleWriter writer = new ConsoleWriter(System.out));

In the Test

ByteArrayOutputStream outSpy = new ByteArrayOutputStream();
ConsoleWriter writer = new ConsoleWriter(new PrintStream(outSpy));
writer.printSomething();
assertThat(outSpy.toString(), is("expected output"));

Discussion

This way the class under test becomes testable by a simple refactoring, without having the need for indirect redirection of the standard output or obscure interception with a system rule.

Skeg answered 19/1, 2014 at 11:49 Comment(5)
I could not find this ConsoleWriter anywhere in the JDK: where is it ?Embrace
It should probably be mentioned in the answer, but I believe that class was created by user1909402.Luanaluanda
I think ConsoleWriter is the test subject,Rosefish
you wont find a simple consolewrite class in the jdk, but it should be really trivial to implementJiffy
This is probably the most correct answer, but keep in mind if you can't refactor the test subject to do this for any reason, the accepted answer is the solution.Cleghorn
F
22

You can set the System.out print stream via setOut() (and for in and err). Can you redirect this to a print stream that records to a string, and then inspect that ? That would appear to be the simplest mechanism.

(I would advocate, at some stage, convert the app to some logging framework - but I suspect you already are aware of this!)

Forceful answered 13/7, 2009 at 13:19 Comment(3)
That was something that came to my mind but I couldn't believe there is no standard JUnit way to do that. Thanks, Brain. But the credits got to dfa for the actual effort.Fluviomarine
This approach is fraught with problems because the standard output stream is a shared resource used by all parts of your program. It is better to use Dependency Injection to eliminate the direct use of the standard output stream: https://mcmap.net/q/80403/-junit-test-for-system-out-printlnDunstan
Yes. I would second that and perhaps even question a logging assert (better to assert a call onto a logging component or similar)Forceful
A
13

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.
Accidental answered 11/12, 2014 at 8:29 Comment(2)
Thank you so much for creating this library! I have been looking for something like this for such a long time! It's very, very useful as sometimes you simply cannot simplify your code enough to be easily testable, but with a log message you can do wonders!Asci
This looks really promising... but even when I just copy your ATMTest program and run it as a test under Gradle, I'm getting an exception... I've raised an issue on your Github page...Hesitate
S
12

If you were using Spring Boot (you mentioned that you're working with an old application, so you probably aren't but it might be of use to others), then you could use org.springframework.boot.test.rule.OutputCapture in the following manner:

@Rule
public OutputCapture outputCapture = new OutputCapture();

@Test
public void out() {
    System.out.print("hello");
    assertEquals(outputCapture.toString(), "hello");
}
Stereogram answered 27/2, 2017 at 7:46 Comment(1)
I up-voted your answer because I use Spring boot and it set me on the right track. Thanks! However, outputCapture needs to be initialized. (public OutputCapture outputCapture = new OutputCapture();) See docs.spring.io/spring-boot/docs/current/reference/html/…Romanism
G
9

@dfa answer is great, so I took it a step farther to make it possible to test blocks of ouput.

First I created TestHelper with a method captureOutput that accepts the annoymous class CaptureTest. The captureOutput method does the work of setting and tearing down the output streams. When the implementation of CaptureOutput's test method is called, it has access to the output generate for the test block.

Source for TestHelper:

public class TestHelper {

    public static void captureOutput( CaptureTest test ) throws Exception {
        ByteArrayOutputStream outContent = new ByteArrayOutputStream();
        ByteArrayOutputStream errContent = new ByteArrayOutputStream();

        System.setOut(new PrintStream(outContent));
        System.setErr(new PrintStream(errContent));

        test.test( outContent, errContent );

        System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));
        System.setErr(new PrintStream(new FileOutputStream(FileDescriptor.out)));

    }
}

abstract class CaptureTest {
    public abstract void test( ByteArrayOutputStream outContent, ByteArrayOutputStream errContent ) throws Exception;
}

Note that TestHelper and CaptureTest are defined in the same file.

Then in your test, you can import the static captureOutput. Here is an example using JUnit:

// imports for junit
import static package.to.TestHelper.*;

public class SimpleTest {

    @Test
    public void testOutput() throws Exception {

        captureOutput( new CaptureTest() {
            @Override
            public void test(ByteArrayOutputStream outContent, ByteArrayOutputStream errContent) throws Exception {

                // code that writes to System.out

                assertEquals( "the expected output\n", outContent.toString() );
            }
        });
}
Giraudoux answered 2/9, 2012 at 4:25 Comment(1)
i tried this helper but the result still failed, even the expected output & actual output same. Possible due to EOL or whitespace, but not sure how to prevent that.Ohare
N
8

Based on @dfa's answer and another answer that shows how to test System.in, I would like to share my solution to give an input to a program and test its output.

As a reference, I use JUnit 4.12.

Let's say we have this program that simply replicates input to output:

import java.util.Scanner;

public class SimpleProgram {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print(scanner.next());
        scanner.close();
    }
}

To test it, we can use the following class:

import static org.junit.Assert.*;

import java.io.*;

import org.junit.*;

public class SimpleProgramTest {
    private final InputStream systemIn = System.in;
    private final PrintStream systemOut = System.out;

    private ByteArrayInputStream testIn;
    private ByteArrayOutputStream testOut;

    @Before
    public void setUpOutput() {
        testOut = new ByteArrayOutputStream();
        System.setOut(new PrintStream(testOut));
    }

    private void provideInput(String data) {
        testIn = new ByteArrayInputStream(data.getBytes());
        System.setIn(testIn);
    }

    private String getOutput() {
        return testOut.toString();
    }

    @After
    public void restoreSystemInputOutput() {
        System.setIn(systemIn);
        System.setOut(systemOut);
    }

    @Test
    public void testCase1() {
        final String testString = "Hello!";
        provideInput(testString);

        SimpleProgram.main(new String[0]);

        assertEquals(testString, getOutput());
    }
}

I won't explain much, because I believe the code is readable and I cited my sources.

When JUnit runs testCase1(), it is going to call the helper methods in the order they appear:

  1. setUpOutput(), because of the @Before annotation
  2. provideInput(String data), called from testCase1()
  3. getOutput(), called from testCase1()
  4. restoreSystemInputOutput(), because of the @After annotation

I didn't test System.err because I didn't need it, but it should be easy to implement, similar to testing System.out.

Nickell answered 6/6, 2018 at 13:9 Comment(0)
F
3

If you are using JUnit 5 with the Jupiter API, I highly recommend trying the junit-pioneer library. It provides a robust solution for reading from the standard input (System.in) and writing to the standard output (System.out and System.err). For example, you can use it as follows:

@Test
@StdIo
void out(StdOut out) {
    System.out.print("hello");
    assertEquals("hello", out.capturedLines()[0]);
}

@Test
@StdIo
public void err(StdErr err) {
    System.err.print("hello again");
    assertEquals("hello again", err.capturedLines()[0]);
}

In this case, there is no need to manually redefine System.out and System.err. Moreover, using StdIo, StdOut, StdIn can help avoid potential concurrency problems when running tests in parallel. You can find more information about other use cases in the official docs.

The dependency:

<dependency>
  <groupId>org.junit-pioneer</groupId>
  <artifactId>junit-pioneer</artifactId>
  <version>2.0.1</version>
  <scope>test</scope>
</dependency>
  • You can check the latest version of the dependency here.
  • The junit-pioneer GitHub repository is right here.
Fi answered 24/5, 2023 at 15:0 Comment(0)
P
2

Full JUnit 5 example to test System.out (replace the when part):

package learning;

import static org.assertj.core.api.BDDAssertions.then;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class SystemOutLT {

    private PrintStream originalSystemOut;
    private ByteArrayOutputStream systemOutContent;

    @BeforeEach
    void redirectSystemOutStream() {

        originalSystemOut = System.out;

        // given
        systemOutContent = new ByteArrayOutputStream();
        System.setOut(new PrintStream(systemOutContent));
    }

    @AfterEach
    void restoreSystemOutStream() {
        System.setOut(originalSystemOut);
    }

    @Test
    void shouldPrintToSystemOut() {

        // when
        System.out.println("example");

        then(systemOutContent.toString()).containsIgnoringCase("example");
    }
}
Portemonnaie answered 31/5, 2018 at 12:31 Comment(0)
B
1

for out

@Test
void it_prints_out() {

    PrintStream save_out=System.out;final ByteArrayOutputStream out = new ByteArrayOutputStream();System.setOut(new PrintStream(out));

    System.out.println("Hello World!");
    assertEquals("Hello World!\r\n", out.toString());

    System.setOut(save_out);
}

for err

@Test
void it_prints_err() {

    PrintStream save_err=System.err;final ByteArrayOutputStream err= new ByteArrayOutputStream();System.setErr(new PrintStream(err));

    System.err.println("Hello World!");
    assertEquals("Hello World!\r\n", err.toString());

    System.setErr(save_err);
}
Beekman answered 4/5, 2017 at 20:15 Comment(1)
For this sort of setup and teardown logic I would use an @Rule, rather than do it inline in your test. Notably, if your assertion fails the second System.setOut/Err call will not be reached.Birkle
S
1

If the function is printing to System.out, you can capture that output by using the System.setOut method to change System.out to go to a PrintStream provided by you. If you create a PrintStream connected to a ByteArrayOutputStream, then you can capture the output as a String.

// Create a stream to hold the output
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos);
// IMPORTANT: Save the old System.out!
PrintStream old = System.out;
// Tell Java to use your special stream
System.setOut(ps);
// Print some output: goes to your special stream
System.out.println("Foofoofoo!");
// Put things back
System.out.flush();
System.setOut(old);
// Show what happened
System.out.println("Here: " + baos.toString());
Sequestered answered 13/8, 2020 at 1:56 Comment(0)
F
0

You don't want to redirect the system.out stream because that redirects for the ENTIRE JVM. Anything else running on the JVM can get messed up. There are better ways to test input/output. Look into stubs/mocks.

Finkelstein answered 13/10, 2012 at 9:10 Comment(0)
M
0

Although this question is very old and has already very good answers I want to provide an alternative. I liked the answer of dfa however I wanted to have something reusable in different projects without copying the configuration and so I created a library out of it and wanted to contribute back to the community. It is called Console Captor and you can add it with the following snippet:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>consolecaptor</artifactId>
    <version>1.0.0</version>
    <scope>test</scope>
</dependency>

Example class

public class FooService {

    public void sayHello() {
        System.out.println("Keyboard not responding. Press any key to continue...");
        System.err.println("Congratulations, you are pregnant!");
    }

}

Unit test

import static org.assertj.core.api.Assertions.assertThat;

import nl.altindag.console.ConsoleCaptor;
import org.junit.jupiter.api.Test;

public class FooServiceTest {

    @Test
    public void captureStandardAndErrorOutput() {
        ConsoleCaptor consoleCaptor = new ConsoleCaptor();

        FooService fooService = new FooService();
        fooService.sayHello();

        assertThat(consoleCaptor.getStandardOutput()).contains("Keyboard not responding. Press any key to continue...");
        assertThat(consoleCaptor.getErrorOutput()).contains("Congratulations, you are pregnant!");
        
        consoleCaptor.close();
    }
}
Microsporangium answered 14/7, 2021 at 20:36 Comment(0)
A
-1

You cannot directly print by using system.out.println or using logger api while using JUnit. But if you want to check any values then you simply can use

Assert.assertEquals("value", str);

It will throw below assertion error:

java.lang.AssertionError: expected [21.92] but found [value]

Your value should be 21.92, Now if you will test using this value like below your test case will pass.

 Assert.assertEquals(21.92, str);
Alexandrina answered 19/7, 2016 at 5:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.