0

I want to test some code that uses 3rd party code that calls kotlin.system.exitProcess(), defined as follows in the standard lib:

@kotlin.internal.InlineOnly
public inline fun exitProcess(status: Int): Nothing {
    System.exit(status)
    throw RuntimeException("System.exit returned normally, while it was supposed to halt JVM.")
}

When exitProcess() is called, the JVM stops and further testing is impossible. I didn't manage to mock calls to exitProcess() with mockk. Is it possible?

Some further information:

The 3rd party lib is Clikt (https://ajalt.github.io/clikt/), a nice library for building a command line interface. A Clikt application parses the command line, and exits if this fails. This may be one of the rare reasons, where calling System.exit is OK. Surely there are more testable solutions, but anyway, when working with 3rd party libs, arguing what could be better done in the lib is obsolete.

What I actually want to test is, that my application writes the expected usage message when called with --help or wrong arguments.

I also tried to mock the call to System.exit() this way: mockkStatic("java.lang.System") every { System.exit(any()) }.throws(RuntimeException("blubb")) which leads to another problem, als all calls to System are mocked then:

io.mockk.MockKException: every/verify {} block were run several times. Recorded calls count differ between runs
Round 1: class java.lang.System.getProperty(kotlin.ignore.old.metadata), class java.lang.System.exit(-630127373)
Round 2: class java.lang.System.exit(158522875)

Funnily enough, I managed to test this in Java with jmockit, like this:

public class MainTestJ {
    private ByteArrayOutputStream consoleOut;

    @BeforeEach
    public void mockConsole() {
        consoleOut = new ByteArrayOutputStream();
        System.setOut(new PrintStream(consoleOut));
    }

    @Test
    public void can_mock_exit() {
        new MockUp<System>() {
            @Mock
            public void exit(int exitCode) {
                System.out.println("exit called");
            }
        };
        assertThatThrownBy(() -> {
            new Main().main(new String[] { "--help" });
        }).isInstanceOf(RuntimeException.class);
        assertThat(consoleOut.toString()).startsWith("Usage: bla bla ...");
    }
}
tchick
  • 357
  • 4
  • 14
  • I'd reconsider the 3rd party code you are using. either it is not meant to be used as a library, so you are wrong in using it anyway, or it's just crap since no library should **ever** terminate the process, they should let that decision to the user code.. – Giacomo Alzetta Dec 19 '18 at 09:18
  • @GiacomoAlzetta I agree about the 3rd party library, It definitely shouldn't be doing that. However the question is intriguing. – Dean Dec 19 '18 at 10:56
  • @tchick could you post your attempts to stub the function? – Dean Dec 19 '18 at 10:56
  • please share the name of a third party code/library if it's public. And some code that calls somethind, that calls exit. Then it will be more clear how it can be done. – While True Dec 19 '18 at 11:50
  • I edited the posting to provide some further information about the 3rd party lib, the test setup and some more experiments. – tchick Dec 20 '18 at 14:38
  • Thanks for the additional information. You are right that this is a rare case where maybe it is ok. Have you tried PowerMock to mock static `System.exit()`? – Dean Dec 21 '18 at 10:54
  • No, I didn't tried PowerMock, but jmockit, as described, which I like more than PowerMock. – tchick Dec 21 '18 at 23:17

1 Answers1

1

I played with this for a while attempting to get it to work but unfortunately it is not possible. There are several difficulties with mocking this function; that it is a Top Level function and that it returns Nothing. Each of these can be overcome, but what makes it impossible is that the function is inlined. Inlined kotlin functions do not produce methods in the bytecode, it is as it says on the tin, inlined. The trouble is that Mockk and other mocking libraries use bytecode instructions when mocking. Refer to this issue for more information.

Your best alternative is not to attempt to mock this function at all, but instead mock the call into the third party library you are using. After all, you should not be testing this third party code, only your own code. Better yet, maybe you should seek an alternative library. As has already been stated in the comments, a third party library should not be exiting the process, this should be left up to the client code.

Dean
  • 1,833
  • 10
  • 28