Search code examples
scalaunit-testingsbtspecs2testability

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


I have been developing a command-line tool which calls System.exit() (don't want to use exceptions instead of) on certain inputs.

I am familiar with Java: How to test methods that call System.exit()? and its the most elegant approach.

Unfortunately, it is not enough pure, due to I had to add the dependencies to system-rules, junit-interface

Is there any common pattern for dealing with System.exit in specs2 which is more pure than my current approach which don't use specs2?

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.ExpectedSystemExit;

public class ConverterTest {
    @Rule
    public final ExpectedSystemExit exit = ExpectedSystemExit.none();

    @Test
    public void emptyArgs() {
        exit.expectSystemExit();
        Converter.main(new String[]{});
    }

    @Test
    public void missingOutArgument() {
        exit.expectSystemExitWithStatus(1);
        Converter.main(new String[]{"--in", "src/test/resources/078.xml.gz"});
    }
}

Solution

  • If you really wish to go with a method using System.exit(), the simplest way to test it was actually called is to replace your SecurityManager with one that'll throw an ExitException (subclassing SecurityException) when System.exit() is called:

    class SystemExitSpec

    import java.security.Permission
    
    import org.specs2.mutable.Specification
    import org.specs2.specification.BeforeAfterAll
    
    sealed case class ExitException(status: Int) extends SecurityException("System.exit() is not allowed") {
    }
    
    sealed class NoExitSecurityManager extends SecurityManager {
      override def checkPermission(perm: Permission): Unit = {}
    
      override def checkPermission(perm: Permission, context: Object): Unit = {}
    
      override def checkExit(status: Int): Unit = {
        super.checkExit(status)
        throw ExitException(status)
      }
    }
    
    
    abstract class SystemExitSpec extends Specification with BeforeAfterAll {
    
      sequential
    
      override def beforeAll(): Unit = System.setSecurityManager(new NoExitSecurityManager())
    
      override def afterAll(): Unit = System.setSecurityManager(null)
    }
    

    test ConverterSpec

    import org.specs2.execute.Failure
    
    import scala.io.Source
    
    class ConverterSpec extends SystemExitSpec {
    
    "ConverterSpec" should {
    
        "empty args" >> {
          try {
            Converter.main(Array[String]())
            Failure("shouldn't read this code")
          } catch {
            case e: ExitException =>
              e.status must_== 1
          }
          1 must_== 1
        }
    }