How to override spring application properties in test classes? Spring’s @ContextConfiguration breaks when using Kotlin inner classes

Viewed 1522

I’m currently trying to change some Spring configuration properties in test code (they aren’t static, that’s why). There’s this odd thing when I try to solve my problem with @ContextConfiguration(initializers = [MyTestClass.Initializer::class]).

and in MyTestClass I defined this:

inner class Initializer : ApplicationContextInitializer<ConfigurableApplicationContext> {
    override fun initialize(applicationContext: ConfigurableApplicationContext) {
        val values = TestPropertyValues.of("spring.datasource.url=" + postgresqlContainer.jdbcUrl)
        values.applyTo(applicationContext)
    }
}

(I’m using Testcontainers here... how to get this working might be a separate question, feel free to help me out.) postgresqlContainer is a member of MyTestClass that I want to access. When I run the test I just get an error:

Caused by: java.lang.IllegalArgumentException: No argument provided for a required parameter: instance of fun com.example.MyTestClass.Initializer.<init>(): com.example.MyTestClass.Initializer

Huh, ok, so I kept debugging a bit and I think it’s Spring’s BeanUtils that isn’t able to handle Kotlin inner classes. If I remove the inner keyword from my inner class BeanUtils can create an instance – doesn’t help me of course, since I need access to the property of the outer class.

I wrote a little test to assert my suspicion:

import io.kotlintest.specs.StringSpec
import org.springframework.beans.BeanUtils

class Thing {
    inner class InnerThing {

    }
}

class BeanUtilTest: StringSpec({
    "instantiate inner class" {
        BeanUtils.instantiateClass(Thing.InnerThing::class.java)
        // fails :-(
    }
})

Question: Is there a workaround? How can I override application properties inside my test in Kotlin?

1 Answers

I just ran into this and, after a long time trying to figure out what was going on, I finally came up with a solution.

You can use a companion object as follows (e.g., for MySql):

@Testcontainers
@ExtendWith(SpringExtension::class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(initializers = [ExampleIntegrationTest.Companion.Initializer::class])
class ExampleIntegrationTest {

    companion object {

        @Container
        @JvmField
        val mySqlContainer = KotlinMySqlContainer()

        class Initializer : ApplicationContextInitializer<ConfigurableApplicationContext> {
            override fun initialize(configurableApplicationContext: ConfigurableApplicationContext) {

                TestPropertyValues.of(
                    "spring.datasource.url=" + mySqlContainer.jdbcUrl,
                    "spring.datasource.username=" + mySqlContainer.username,
                    "spring.datasource.password=" + mySqlContainer.password
                ).applyTo(configurableApplicationContext.environment)
            }
        }
    }
    ...
}
Related