1

I'm trying to test console output of another program using JUnit. I followed the answers given in here

Here is my JUnit class

import static org.junit.Assert.*;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import org.junit.*;

public class MainTest {
        private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
        private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();

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

        @Test
        public void test01_initTest() {
                String[] arguments = {"a", "b"};
                Main.main(arguments);
                String expected = "hello";
                assertTrue(expected == outContent.toString());
        }

        @After
        public void cleanUpStreams() {
            System.setOut(null);
            System.setErr(null);
        }
}

When I run this program in eclipse, neither do I see the console output and the test run doesn't end. However if I printout on console directly from test01_initTest(), the test passes. To check if Main.main() is printing out I also tried the below code and I do see the console output in this case

import static org.junit.Assert.*;
import org.junit.*;

public class MainTest {
        @Test
        public void test01_initTest() {
                String[] arguments = {"a", "b"};
                Main.main(arguments);
        }
}

I tired a lot of things but I'm unable to figure out what I'm doing wrong here. Can someone please help me out.

Community
  • 1
  • 1
Sanket
  • 746
  • 3
  • 13
  • 26
  • 1
    Why are you using assertTrue? Use AssertEquals(expected, outContent.toString()). `==` doesn't do what you think it does. – Falmarri Dec 10 '14 at 23:23
  • Hi Falmarri, I tried that too but the result is same. I do not see any output on the console and the test run doesn't end – Sanket Dec 10 '14 at 23:27
  • 1
    We would have to see the code of `Main`. You don't call System.exit() in Main do you? – dkatzel Dec 11 '14 at 02:59
  • Wait, why are you expecting console output? You're setting your stdout and stderr to other streams. – Falmarri Dec 11 '14 at 03:48
  • Hey dkatzel, Yes I use System.exit() in my program. Is that the issue? – Sanket Dec 11 '14 at 04:50

1 Answers1

1

JUnit can not complete normally if the code you are testing calls System.exit().

If that main thread terminates, JUnit will just hang.

A workaround is to use a SecurityManager to prevent calls to System.exit()

In this other StackOverflow answer, I describe how to write such a SecurityManager:

Getting list of tests from JUnit command line

(code copied here just in case).

This SecurityManager will throw a custom TriedToExitException when System.exit() is called and preserves the exit code so you can catch the exception and test that the correct exit code was used.

/**
 * A special implementation of {@link SecurityManager}
 * that will throw a {@link TriedToExitException}
 * if {@link System#exit(int)} is called.
 * This is useful for testing main methods
 * without shutting
 * down the jvm running junit.
 * Tests can catch {@link TriedToExitException}
 * to figure out what exit code was set.
 */
public static final SecurityManager NON_EXITABLE_MANAGER = new SecurityManager(){

    @Override
    public void checkPermission(Permission perm) {
        //allow everything
    }
    /**
     * Throws a {@link TriedToExitException} instead of exiting.
     * <p/>
     * {@inheritDoc}
     */
    @Override
    public void checkExit(int status) {
        throw new TriedToExitException(status);
    }

};

public static final class TriedToExitException extends SecurityException{
    private static final long serialVersionUID = 1L;
    private final int exitCode;

    public TriedToExitException(int exitCode){
        this.exitCode=exitCode;
    }

    @Override
    public String getMessage() {
        return String.format("tried to System.exit(%d)",exitCode);
    }

    public int getExitCode() {
        return exitCode;
    }

Then in your unit tests you replace the SecurityManager with our version that stops calls to exit (and save the old SecurityManager so we can restore it after the test)

private SecurityManager previousManager=null;


@Before
protected void replaceManager() throws Throwable {
    previousManager = System.getSecurityManager();
    System.setSecurityManager(NON_EXITABLE_MANAGER);
}

@After
protected void restoreManager() {
    System.setSecurityManager(previousManager);
}

 @Test
public void testCallToExit() throws IOException{

     try{
       //something that calls System.exit
        fail("should call exit");
     }catch(TriedToExitException e){
         //make sure we get correct exit code
         assertEquals(-1, e.getExitCode());

     }
}
Community
  • 1
  • 1
dkatzel
  • 31,188
  • 3
  • 63
  • 67