Kotest System Extensions

Kotest System Extensions provide assertions for system calls related to Java’s java.lang.System class. These extensions are used to test exceptions from system calls and verify behavior such as system output or system exit.

System Extensions

If you need to test code that uses java.lang.System, Kotest provides extensions that can modify the system and restore it after each test. These extensions are available only on the JVM.

To use these extensions, add the dependency to your project:

io.kotest:kotest-extensions-jvm:${version}

System Environment

The system environment extension lets you simulate how the system environment works. In other words, it lets you simulate what you get from System.getenv().

Kotest provides several extension functions that provide a system environment for a specific scope:

withEnvironment("FooKey", "BarValue") {
    System.getenv("FooKey") shouldBe "BarValue" // System environment overridden!
}

This extension can also use multiple values through a Map or a list of Pairs.

withEnvironment(mapOf("FooKey" to "BarValue", "BarKey" to "FooValue")) {
  // Use FooKey and BarKey
}

This function adds keys and values if they do not exist in the current environment, and overrides them if they do exist. Keys not touched by the function remain in the environment unchanged.

Instead of extension functions, you can also apply this functionality to a wider scope using the provided listeners. There are alternatives for the Spec/per-test level and for the project level.

import io.kotest.core.spec.style.FreeSpec
import io.kotest.extensions.system.SystemEnvironmentTestListener
import io.kotest.matchers.shouldBe

class MyTest : FreeSpec() {

    override fun listeners() = listOf(SystemEnvironmentTestListener("foo", "bar"))

    init {
        "MyTest" {
            System.getenv("foo") shouldBe "bar"
        }
    }
}
import io.kotest.core.config.AbstractProjectConfig
import io.kotest.extensions.system.SystemEnvironmentProjectListener

class ProjectConfig : AbstractProjectConfig() {
    override fun listeners() = listOf(SystemEnvironmentProjectListener("foo", "bar"))
}

System Property Extension

You can override system properties, System.getProperties(), in the same way as the environment extension:

withSystemProperty("foo", "bar") {
    System.getProperty("foo") shouldBe "bar"
}

It is also provided as a listener:

import io.kotest.core.spec.style.FreeSpec
import io.kotest.extensions.system.SystemPropertyTestListener
import io.kotest.matchers.shouldBe

class MyTest : FreeSpec() {

    override fun listeners() = listOf(SystemPropertyTestListener("foo", "bar"))

    init {
        "MyTest" {
            System.getProperty("foo") shouldBe "bar"
        }
    }
}

System Security Manager

Similarly, the system security manager extension lets you override the system security manager, System.getSecurityManager().

withSecurityManager(myManager) {
    // Usage of security manager
}

It is also provided as a listener:

import io.kotest.core.spec.style.FreeSpec
import io.kotest.extensions.system.SecurityManagerListener

class MyTest : FreeSpec() {

    override fun listeners() = listOf(SecurityManagerListener(myManager))

    init {
        // Use my security manager
    }
}

System Exit Extension

Sometimes you may want to test whether code calls System.exit. For this, you can use the system exit listener. The listener throws an exception when System.exit is called, so you can catch and verify it:

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FreeSpec
import io.kotest.extensions.system.SpecSystemExitListener
import io.kotest.extensions.system.SystemExitException
import io.kotest.matchers.shouldBe

class MyTest : FreeSpec() {

    override fun listeners() = listOf(SpecSystemExitListener)

    init {
        "Catch exception" {
            val thrown: SystemExitException = shouldThrow<SystemExitException> {
                System.exit(22)
            }

            thrown.exitCode shouldBe 22
        }
    }
}

no-stdout/no-stderr Listeners

You may want to ensure that no debug messages are left behind, or that logging always uses a logger.

For this, Kotest provides NoSystemOutListener and NoSystemErrListener. These listeners do not allow messages to be printed directly to System.out or System.err, respectively:

    // In Project or in Spec
    override fun listeners() = listOf(NoSystemOutListener, NoSystemErrListener)

Locale/Timezone Listeners

Some code uses or is sensitive to the default Locale and Timezone. Instead of manipulating system defaults directly, let Kotest handle it.

withDefaultLocale(Locale.FRANCE) {
  println("My locale is now France! Très bien!")
}

withDefaultTimeZone(TimeZone.getTimeZone(ZoneId.of("America/Sao_Paulo"))) {
  println("My timezone is now America/Sao_Paulo! Muito bem!")
}

They are also provided as listeners:

// In Project or in Spec
override fun listeners() = listOf(
    LocaleTestListener(Locale.FRANCE),
    TimeZoneTestListener(TimeZone.getTimeZone(ZoneId.of("America/Sao_Paulo")))
)

References