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>
kotlin
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:
kotlin
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:
kotlin
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:
- SimpleProperties - That reads values both from Environment and Properties. Test Class. It's even possible to see both extensions used at once
- SimpleFeatureThrottler - A similar approach can be seen in this test class
Top comments (0)