Can't mock java.lang.System#exit(int) method with PowerMock

Viewed 2353

My application has a flow that in the end of it the method System.exit(int) is being called.

I'm trying to test this flow by running a test using TestNG.

However, when running the test I'm getting this weird message although the test was completed:

enter image description here

Just for finding the root cause, I removed the System.exit(int) from the real flow and the test passed as expected, so the problem here is the System.exit(int) method.

In order to solve this issue I've tried to mock the problematic method but couldn't find the right way to do it. Here is what I did

  1. I added java.lang.System.class under @PrepareForTest in the tests class.
  2. added PowerMockito.mockStatic(java.lang.System.class) in the test
  3. I've tried to mock the method in two ways:

    a.

        PowerMockito.replace(PowerMockito.method(System.class, "exit", int.class))
        .with((proxy, method, args) -> null);
    

    When running this way looks like the mock is not working, because I'm getting the same message at the end of the test which I also got when not applying any mocks on System.exit(int)

enter image description here

b.

PowerMockito.doNothing().when(System.class, "exit", Mockito.any());

In this way I'm getting this exception at the beginning of the test:

org.powermock.reflect.exceptions.MethodNotFoundException: No method found with name 'exit' with parameter types: [ <none> ] in class java.lang.System.

I already mocked some methods in this ways, not sure why with System.exit(int) it is not working.

Any ideas? Thanks

1 Answers

Interesting question, I didn't know either, but apparently this is possible without the use of Powermock, through the use of SecurityManagers. Quoting from the original post:

Today I was writing test for one of our command line tools, and I had this problem where the method dumping everything, which I really needed to be called since that’s the outcome I was checking, also called System.exit(). I had to find a way to test this anyway. I thought about using PowerMock and mocking system but that would have been complicated because I would have to find the exact class calling the System.exit(). So here is another solution to avoid the System.exit to exit (yes that’s possible I didn’t know about that either).

The secrets lays in the SecurityManager mechanism of Java, this class not only allows you to check permissions, but also to check exit event. Therefore you can throw an exception if you want to stop the exit

Below is a full sample I tested in IJ. Please note the sample is supposed to fail on purpose with:

java.lang.AssertionError: 
Expected: is <10>
     but: was <5>
Expected :is <10>
Actual   :<5>

package com.example;

import org.junit.Test;

import java.security.Permission;

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;

public class SystemExitTest {

    @Test
    public void shouldExitWithSpecificCode() {
        //save the initial security manager (probably null)
        SecurityManager initialSecurityManger = System.getSecurityManager();
        try {
            // set security manager
            System.setSecurityManager(new NoExitSecurityManager());

            // execute code under test
            new MyClass().exit(5);

            // ensure this point is not reached by any chance
            fail("Should've thrown ExitException");
        } catch (ExitException e) {
            // validate exit code
            assertThat(e.status, is(10)); // <== this fails on purpose
        } finally {
            // restore initial security manager (otherwise a new ExitException will be thrown when the JVM will actually exit)
            System.setSecurityManager(initialSecurityManger);
        }
    }


    // class under test
    public static class MyClass {
        public void exit(int code) {
            System.exit(code);
        }
    }

    // exception to be thrown by security manager when System.exit is called
    public static class ExitException extends SecurityException {
        public final int status;

        public ExitException(int status) {
            this.status = status;
        }
    }


    // custom security manager
    public static class NoExitSecurityManager extends SecurityManager {
        @Override
        public void checkPermission(Permission perm) {
        }

        @Override
        public void checkPermission(Permission perm, Object context) {
        }

        @Override
        public void checkExit(int status) {
            super.checkExit(status);
            throw new ExitException(status);
        }
    }
}
Related