Java: How to test methods that call System.exit()?

2019-01-01 04:59发布

I've got a few methods that should call System.exit() on certain inputs. Unfortunately, testing these cases causes JUnit to terminate! Putting the method calls in a new Thread doesn't seem to help, since System.exit() terminates the JVM, not just the current thread. Are there any common patterns for dealing with this? For example, can I subsitute a stub for System.exit()?

[EDIT] The class in question is actually a command-line tool which I'm attempting to test inside JUnit. Maybe JUnit is simply not the right tool for the job? Suggestions for complementary regression testing tools are welcome (preferably something that integrates well with JUnit and EclEmma).

15条回答
听够珍惜
2楼-- · 2019-01-01 05:40

Use Runtime.exec(String command) to start JVM in a separate process.

查看更多
素衣白纱
3楼-- · 2019-01-01 05:43

You actually can mock or stub out the System.exit method, in a JUnit test.

For example, using JMockit you could write (there are other ways as well):

@Test
public void mockSystemExit(@Mocked("exit") System mockSystem)
{
    // Called by code under test:
    System.exit(); // will not exit the program
}


EDIT: Alternative test (using latest JMockit API) which does not allow any code to run after a call to System.exit(n):

@Test(expected = EOFException.class)
public void checkingForSystemExitWhileNotAllowingCodeToContinueToRun() {
    new Expectations(System.class) {{ System.exit(anyInt); result = new EOFException(); }};

    // From the code under test:
    System.exit(1);
    System.out.println("This will never run (and not exit either)");
}
查看更多
十年一品温如言
4楼-- · 2019-01-01 05:45

Create a mock-able class that wraps System.exit()

I agree with EricSchaefer. But if you use a good mocking framework like Mockito a simple concrete class is enough, no need for an interface and two implementations.

Stopping test execution on System.exit()

Problem:

// do thing1
if(someCondition) {
    System.exit(1);
}
// do thing2
System.exit(0)

A mocked Sytem.exit() will not terminate execution. This is bad if you want to test that thing2 is not executed.

Solution:

You should refactor this code as suggested by martin:

// do thing1
if(someCondition) {
    return 1;
}
// do thing2
return 0;

And do System.exit(status) in the calling function. This forces you to have all your System.exit()s in one place in or near main(). This is cleaner than calling System.exit() deep inside your logic.

Code

Wrapper:

public class SystemExit {

    public void exit(int status) {
        System.exit(status);
    }
}

Main:

public class Main {

    private final SystemExit systemExit;


    Main(SystemExit systemExit) {
        this.systemExit = systemExit;
    }


    public static void main(String[] args) {
        SystemExit aSystemExit = new SystemExit();
        Main main = new Main(aSystemExit);

        main.executeAndExit(args);
    }


    void executeAndExit(String[] args) {
        int status = execute(args);
        systemExit.exit(status);
    }


    private int execute(String[] args) {
        System.out.println("First argument:");
        if (args.length == 0) {
            return 1;
        }
        System.out.println(args[0]);
        return 0;
    }
}

Test:

public class MainTest {

    private Main       main;

    private SystemExit systemExit;


    @Before
    public void setUp() {
        systemExit = mock(SystemExit.class);
        main = new Main(systemExit);
    }


    @Test
    public void executeCallsSystemExit() {
        String[] emptyArgs = {};

        // test
        main.executeAndExit(emptyArgs);

        verify(systemExit).exit(1);
    }
}
查看更多
登录 后发表回答