DEV Community

loading...
Kotest

Testing System Properties and System Environment with Kotest

Leonardo Colman Lopes
Kotlin enthusiast, enjoys open source as a hobby and is really into software testing!
Updated on ・3 min read

TL;DR: Test code that uses System.getenv() or System.getProperty() with easy-to-use functions with Kotest

Hello Kotliners!

In this article we'll discuss a little bit two features of Kotest:

  • System Environment extensions
  • System Property extensions

They're very similar on their implementation and use, and are very easy to use!

The problem

In the JVM world, some (many) times our code depends on what the system provides us, usually through Environment Variables or Properties passed to the JVM.

However, neither is very easy to test when we want to use it.

  • The System.getenv() map is immutable. We can't set it up using plain Java
  • Messing with System.setProperty() manually forces us to do all the setup and tear-down, and becomes very verbose and cumbersome
Photo by Victoria Heath on Unsplash

The solution

Kotest provides some extensions and listeners to handle these scenarios. The complete documentation can be found here

System Environment Extensions

If the piece of code under test is small enough and used only once, we can use the withSystemEnvironment. It has overloads, but we'll use the key and value one. There's an overload expecting a Pair<K,V> and one expecting a Map<K,V>

class EnvironmentTest : FunSpec() {
    init {
        test("Changing environment") {
            withSystemEnvironment("fooKey", "barValue") {
                System.getenv("fooKey") shouldBe "barValue"
            }
        }
    }
}

System Properties Extensions

In the same fashion as above, we have the function withSystemProperties, that works very similarly:

class PropertyTest : FunSpec() {
    init {
        test("Changing System Properties") {
            withSystemProperties(mapOf("fooKey" to "barValue", "booKey" to "batValue")) {
                System.getProperty("fooKey") shouldBe "barValue"
                System.getProperty("booKey") shouldBe "batValue"
            }
        }
    }
}

Cleanup

After the execution, the previous values will be set to what they were before. What is already in the map will remain untouched during the execution.

Test Listeners

Perhaps your code require the specific properties in every test. You can configure the SystemPropertyTestListener and the SystemEnvironmentTestListener for that:

class SystemEnvironmentTestListenerTest : FunSpec() {

    override fun listeners() = listOf(
            SystemEnvironmentTestListener("fooKeyEnv", "barValueEnv"),
            SystemPropertyTestListener("fooKeyProp", "barValueProp")
        )

    init {
        test("System Property") {
            System.getProperty("fooKeyProp") shouldBe "barValueProp"
        }
        test("System Environment") {
            System.getenv("fooKeyEnv") shouldBe "barValueEnv"
        }
    }
}

The original values will reset after the test execution.

Project Listener

You can also set these globally by using the SystemEnvironmentProjectListener and the SystemPropertyProjectListener. More information on Project Configuration can be found in Kotest's Docs

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

Override Mode

All the above extensions accept a parameter of the enum OverrideMode.

  • SetOrOverride - If the key we want to set is already in the properties/environment, we want to override it for the test
  • SetOrIgnore - If the key is already present, we'll not override it and the test will continue
  • SetOrError (default) - If the key is already present, interrupt the test with an Exception

These are very useful if you need to use, for example, a property in the global listener, but want to override it in a specific test.

withSystemProperty("foo", "bar", OverrideMode.SetOrOverride) {
    // foo is overrided here
}

Extensions in action

I've published 2 small and simple libs that exercise these listeners and extensions:

Discussion (0)