Wednesday, September 16, 2009

How to write an automated test for code that calls System.exit()

The other day I came across some code that during certain conditions was expected to call System.exit(). Naturally I wanted test coverage for that code too, but since any test calling the method in question would terminate the entire testrun I had to find a different approach. Here is one strategy that can be used.

Assume we need to write a test for the following simple method. (Yep, I just made this one up.)


    public void inconceivable() {
        System.exit(42);
    }

I can not just call the method in a JUnit test like this (but feel free to try it :-)).

    @Test
    public void shouldCallSystemExit() {
        new ObjectUnderTest().inconceivable();
        // Assert what now?
    }

One way to make it possible is by utilizing a SecurityManager. By installing a custom security manager I can prevent the exit from actually happen. The following implementation overrides checkExit() to throw an exception whenever a call to System.exit() is made. By also overriding checkPermission() to do nothing, I allow myself to remove the security manager again, after it has been installed.

class ExceptionOnExitSecurityManager extends SecurityManager {
    @Override
    public void checkExit(int status) {
        super.checkExit(status);
        throw new SystemExitException(status);
    }

    @Override
    public void checkPermission(Permission perm) {
        // Override to allow removal of this security manager
    }
}

A custom exception makes it really easy to access the status code.

class SystemExitException extends SecurityException {
    int statusCode;
    SystemExitException(int statusCode) {
        this.statusCode = statusCode;
    }
}

Now I am ready to write my test.

    @Test
    public void shouldCallSystemExit() {
        System.setSecurityManager(new ExceptionOnExitSecurityManager());
        try {
            new ObjectUnderTest().inconceivable();
            fail("Did not call System.exit()");
        } catch (SystemExitException e) {
            assertEquals(42, e.statusCode);
        }
        System.setSecurityManager(null);
    }

This test will fail nicely if System.exit() is never called, or if it is called with a different status code than expected. Now the expected behaviour is documented and verified, and my test coverage moved up a notch.

Do you have a different strategy for this type of situations, or do you see any problems with this approach? Please share it in the comments!

No comments: