kotlintestingkotest

kotest change environment variables


I am writing tests for Ktor app using Kotests, but stumbled into the problem how can I change env variables for tests preferably globally. I have tried adding withEnvironment but it throw quite strange error into me

Unable to make field private final java.util.Map java.util.Collections$UnmodifiableMap.m accessible: module java.base does not "opens java.util" to unnamed module @3daa422a
java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.Map java.util.Collections$UnmodifiableMap.m accessible: module java.base does not "opens java.util" to unnamed module @3daa422a
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)

my test file looks like this

class VisitorSpec : FreeSpec({
    val ds = createDataSourceTest()

    val visitor = RegisterVisitorDTO(
        email = TestConstants.VISITOR_EMAIL,
        username = TestConstants.VISITOR_USERNAME,
        password = TestConstants.PASSWORD,
        firstName = TestConstants.VISITOR_FIRST_NAME,
        lastName = TestConstants.VISITOR_LAST_NAME,
        gender = TestConstants.VISITOR_GENDER,
        birthday = TestConstants.VISITOR_BIRTHDAY,
    )


    "check visitor routes" - {

        val loginData = LoginDTO(TestConstants.VISITOR_EMAIL + 0, TestConstants.PASSWORD)

        "can get list of visitors with correct query" {
            withEnvironment(
                mapOf(
                    "POSTGRES_URL" to "jdbc:postgresql://localhost:5432/test",
                    "POSTGRES_USERNAME" to "test_user",
                    "POSTGRES_PASSWORD" to "test_pass"
                )
            ) {
                testApplication {
                    val client = getClient(ds)
                    repeat(6) {
                        registerConfirmedUser(
                            client, visitor.copy(
                                email = "${TestConstants.VISITOR_EMAIL}$it",
                                username = "${TestConstants.VISITOR_USERNAME}$it",
                            )
                        )
                    }

                    val accessToken = loginUser(client, loginData).run { this.body<LoginResponseDTO>().accessToken }
                    client.get("/api/v1/visitors?page=1&count=5") {
                        header("Authorization", "Bearer $accessToken")
                    }.apply {
                        val response = this.body<VisitorPaginatedResponseDTO>()
                        response.data.size.shouldBe(5)
                        response.totalCount.shouldBe(6)
                        response.currentPage.shouldBe(1)
                    }
                }
            }
        }
...

if I remove

 withEnvironment(
                mapOf(
                    "POSTGRES_URL" to "jdbc:postgresql://localhost:5432/test",
                    "POSTGRES_USERNAME" to "test_user",
                    "POSTGRES_PASSWORD" to "test_pass"
                )
            )

it will just work but with default db, any advice on this?

In some places, it was advised to use

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

but ide tells me that this method is deprecated. Thanks in advance for any advice.


Solution

  • Recent Java versions prohibit modifying the environment variables with the default access settings (JEP 403: Strongly Encapsulate JDK Internals). Kotest and some other testing frameworks that manipulate the environment variables got affected by this, you can find the related issues:

    One solution would be to add the arguments to the JVM running the tests that would make the Java Platform Module System allow the access to the API used by the test framework. Here's an answer that explains the arguments: How to set environment variable in Java without 'illegal reflective access'? How to use add-opens?

    The simplest form of the argument, if you are not using Java modules in your code, would be:

    --add-opens java.base/java.util=ALL-UNNAMED
    

    If you are running the tests using Gradle, then you can pass this argument to the jvmArgs of the test task:

    tasks.withType<Test>().named("jvmTest") {
        jvmArgs("--add-opens", "java.base/java.util=ALL-UNNAMED")
    }
    

    Note: modifying the module access in this way could make the tests pass even if some of your code needs illegal access to the JDK internals. Make sure that your code doesn't do that or that you have other tests that check for this without modifying module access rights.


    It seems that some other libraries, like system-stubs, provide a way to modify the environment variables in tests without illegal reflective access to the JDK internals.