views:

5556

answers:

11

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).

+3  A: 

A quick look at the api, shows that System.exit can throw an exception esp. if a securitymanager forbids the shutdown of the vm. Maybe a solution would be to install such a manager.

flolo
+52  A: 

Indeed, Derkeiler.com suggests:

  • Why System.exit ?

Instead of terminating with System.exit(whateverValue), why not throw an unchecked exception? In normal use it will drift all the way out to the JVM's last-ditch catcher and shut your script down (unless you decide to catch it somewhere along the way, which might be useful someday).

In the JUnit scenario it will be caught by the JUnit framework, which will report that such-and-such test failed and move smoothly along to the next.

  • Prevent System.ext to actually exit the JVM:

Try modifying the TestCase to run with a security manager that prevents calling System.exit, then catch the SecurityException.

public class NoExitTestCase extends TestCase 
{

    protected static class ExitException extends SecurityException 
    {
     public final int status;
     public ExitException(int status) 
     {
      super("There is no escape!");
      this.status = status;
     }
    }

    private static class NoExitSecurityManager extends SecurityManager 
    {
     @Override
     public void checkPermission(Permission perm) 
     {
      // allow anything.
     }
     @Override
     public void checkPermission(Permission perm, Object context) 
     {
      // allow anything.
     }
     @Override
     public void checkExit(int status) 
     {
      super.checkExit(status);
      throw new ExitException(status);
     }
    }

    @Override
    protected void setUp() throws Exception 
    {
     super.setUp();
     System.setSecurityManager(new NoExitSecurityManager());
    }

    @Override
    protected void tearDown() throws Exception 
    {
     System.setSecurityManager(null); // or save and restore original
     super.tearDown();
    }

    public void testNoExit() throws Exception 
    {
     System.out.println("Printing works");
    }

    public void testExit() throws Exception 
    {
     try 
     {
      System.exit(42);
     } catch (ExitException e) 
     {
      assertEquals("Exit status", 42, e.status);
     }
    }
}
VonC
Didn't like the first answer, but the second is pretty cool--I hadn't messed with security managers and assumed they were more complicated than that. However, how do you test the security manager/testing mechanism.
Bill K
Works like a charm.
Chris Conway
Make sure the tear down is executed properly, otherwise, your test will fail in a runner such as Eclipse because the JUnit application can't exit! :)
MetroidFan2002
I don't like the solution using the security manager. Seems like a hack to me just to test it.
Nicolai Reuschling
Exactly what i was looking for nice answer.
Paul Whelan
+2  A: 

Calling System.exit() is a bad practice, unless it's done inside a main(). These methods should be throwing an exception which, ultimately, is caught by your main(), who then calls System.exit with the appropriate code.

That doesn't answer the question, though. What if the function being tested IS ultimately the main method? So calling System.exit() might be valid and ok from a design perspective. How do you write a test case for it?
Elie
You shouldn't have to test the main method as the main method should just take any arguments, pass them to a parser method, and then kick start the application. There should be no logic in the main method to be tested.
Thomas Owens
@Elie: In these types of questions there are two valid answers. One answering the question posed, and one asking why the question was based. Both types of answers give a better understanding, and especially both together.
runaros
+11  A: 

One trick we used in our code base was to have the call to System.exit() be encapsulated in a Runnable impl, which the method in question used by default. To unit test, we set a different mock Runnable. Something like this:

private static final Runnable DEFAULT_ACTION = new Runnable(){
  public void run(){
    System.exit(0);
  }
};

public void foo(){ 
  this.foo(DEFAULT_ACTION);
}

/* package-visible only for unit testing */
void foo(Runnable action){   
  // ...some stuff...   
  action.run(); 
}

...and the JUnit test method...

public void testFoo(){   
  final AtomicBoolean actionWasCalled = new AtomicBoolean(false);   
  fooObject.foo(new Runnable(){
    public void run(){
      actionWasCalled.set(true);
    }   
  });   
  assertTrue(actionWasCalled.get()); 
}
Scott Bale
Simple and elegant. Wish I could vote it up twice.
Bill K
Is this what they call dependency injection?
Thomas Ahle
This example as written is sort of half-baked dependency injection - the dependency is passed to the package-visible foo method (by either the public foo method or the unit test), but the main class still hardcodes the default Runnable implementation.
Scott Bale
+1  A: 

You can use the java SecurityManager to prevent the current thread from shutting down the Java VM. The following code should do what you want:

SecurityManager securityManager = new SecurityManager() {
    public void checkPermission(Permission permission) {
     if ("exitVM".equals(permission.getName())) {
      throw new SecurityException("System.exit attempted and blocked.");
     }
    }
};
System.setSecurityManager(securityManager);
Marc Novakowski
Hm. The System.exit docs say specifically that checkExit(int) will be called, not checkPermission with name="exitVM". I wonder if I should override both?
Chris Conway
The permission name actually seems to be exitVM.(statuscode), i.e. exitVM.0 - at least in my recent test on OSX.
Mark Derricutt
+11  A: 

How about injecting an "ExitManager" into this Methods:

public interface ExitManager {
    void exit(int exitCode);
}

public class ExitManagerImpl implements ExitManager {
    public void exit(int exitCode) {
        System.exit(exitCode);
    }
}

public class ExitManagerMock implements ExitManager {
    public bool exitWasCalled;
    public int exitCode;
    public void exit(int exitCode) {
        exitWasCalled = true;
        this.exitCode = exitCode;
    }
}

public class MethodsCallExit {
    public void CallsExit(ExitManager exitManager) {
        // whatever
        if (foo) {
            exitManager.exit(42);
        }
        // whatever
    }
}

The production code uses the ExitManagerImpl and the test code uses ExitManagerMock and can check if exit() was called and with which exit code.

EricSchaefer
I really like this solution.
Nicolai Reuschling
+2  A: 

I like some of the answers already given but I wanted to demonstrate a different technique that is often useful when getting legacy code under test. Given code like:

public class Foo {
  public void bar(int i) {
    if (i < 0) {
      System.exit(i);
    }
  }
}

You can do a safe refactoring to create a method that wraps the System.exit call:

public class Foo {
  public void bar(int i) {
    if (i < 0) {
      exit(i);
    }
  }

  void exit(int i) {
    System.exit(i);
  }
}

Then you can create a fake for your test that overrides exit:

public class TestFoo extends TestCase {

  public void testShouldExitWithNegativeNumbers() {
    TestFoo foo = new TestFoo();
    foo.bar(-1);
    assertTrue(foo.exitCalled);
    assertEquals(-1, foo.exitValue);
  }

  private class TestFoo extends Foo {
    boolean exitCalled;
    int exitValue;
    void exit(int i) {
      exitCalled = true;
      exitValue = i;
    }
}

This is a generic technique for substituting behavior for test cases, and I use it all the time when refactoring legacy code. It not usually where I'm going to leave thing, but an intermediate step to get the existing code under test.

Jeffrey Fredrick
This tecniques does not stop the conrol flow when the exit() has been called. Use an Exception instead.
Andrea Francia
+8  A: 

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
}
Rogerio
+1  A: 

For VonC's answer to run on JUnit 4, I've modified the code as follows

protected static class ExitException extends SecurityException {
    private static final long serialVersionUID = -1982617086752946683L;
    public final int status;

    public ExitException(int status) {
        super("There is no escape!");
        this.status = status;
    }
}

private static class NoExitSecurityManager extends SecurityManager {
    @Override
    public void checkPermission(Permission perm) {
        // allow anything.
    }

    @Override
    public void checkPermission(Permission perm, Object context) {
        // allow anything.
    }

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

private SecurityManager securityManager;

@Before
public void setUp() {
    securityManager = System.getSecurityManager();
    System.setSecurityManager(new NoExitSecurityManager());
}

@After
public void tearDown() {
    System.setSecurityManager(securityManager);
}
Li Huan
+2  A: 

There are environments where the returned exit code is used by the calling program (such as ERRORLEVEL in MS Batch). We have tests around the main methods that do this in our code, and our approach has been to use a similar SecurityManager override as used in other tests here.

Last night I put together a small JAR using Junit @Rule annotations to hide the security manager code, as well as add expectations based on the expected return code. http://code.google.com/p/junitsystemrules/

Dan Watt
A: 

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

Alexei