Testing code that calls System.exit in Java

doug
Fri, 28 Mar 2014 22:09:54 +0000

It sometimes comes up in the course of unit testing where a unit of code calls System.exit(status). If you've a sane codebase this is normally within a main class. Consider an insane codebase:

Consider

public class DisgruntledPenguin {
    public static boolean happy() {
        System.exit(5);
    }
}

If you want a write a unit test for such code paths you might find yourself stuck. Whenever you run your test, your JUnit test fails and exits unexpectedly, surprise surprise.

Suppose this is your test:

public class DisgruntledPenguinTest {
    @Test
    public void whenInvokeHappy_expectExitWithStatusEqualTo5() {
        DisgruntledPenguin.happy();
        // TODO: Figure out how to determine exit status
    }
}

Solution? I've got one. You set a security manager. Typically in a normal out-of-the-box JVM the security manager is undefined. So we'll have to define one and allows everything /except/ System.exit then use an Exception type to return the exit status to the test to be asserted against. Once you've wrote this class once you can easily reuse it in future tests so make sure to put it in some shared project.

public class ExitDeniedSecurityManager extends SecurityManager {

    public static final class ExitSecurityException extends SecurityException {
        private final int status;

        public ExitSecurityException(final int status) {
            this.status = status;
        }

        public int getStatus() {
            return this.status;
        }
    }

    @Override
    public void checkExit(final int status) {
        throw new ExitSecurityException(status);
    }

    @Override
    public void checkPermission(final Permission perm) {}

    /* TODO: Make sure you override everything with an empty method as above */
}

public class DisgruntledPenguinTest {
    @Before
    public void setUp() {
        System.setSecurityManager(new ExitDeniedSecurityManager());
    }

    @Test
    public void whenInvokeHappy_expectExitWithStatusEqualTo5() {
        try {
            DisgruntledPenguin.happy();
            Assert.fail("Expected exit");
        } catch (ExitSecurityException e) {
            int status = e.getStatus();
            Assert.assertEquals(5, status);
        }
    }
}

If you're a good citizen you'll also record the original SecurityManager using System.getSecurityManager and reset it in tear down but it (probably) makes no difference.

You're welcome.