spring-bootpropertiesspring-boot-test

How can I test Properties class without @SpringBootTest


I want to test Properties class without @SpringBootTest Annotation. But it doesn't work. How can I fix it? I'm using Kotlin, Spring Boot 2.7.18

@ActiveProfiles("test")
@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [TestConfig::class])
class AESTest {
    @Autowired
    lateinit var properties: TestAESProperty

    @Test
    fun decrypt() {
        assertEquals(properties.key, "hello")
        assertEquals(properties.iv, "world")
    }

}
@TestConfiguration
@EnableConfigurationProperties(TestAESProperty::class)
class TestConfig  {
}
@ConstructorBinding
@ConfigurationProperties(prefix = "common.test.aes")
data class TestAESProperty(
    val key: String,
    val iv: String,
)

---- application-test.yml ----------

spring:
  config:
    activate:
      on-profile: test

common:
  test:
    aes:
      key: hello
      iv: world

But, below errors accurs.

Failed to bind properties under 'common.test.aes' to TestAESProperty
Parameter specified as non-null is null: method TestAESProperty.<init>, parameter key
Error creating bean with name 'TestAESProperty'
Could not bind properties to 'TestAESProperty' : prefix=common.test.aes, ignoreInvalidFields=false, ignoreUnknownFields=true; nested exception is org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'common.test.aes' to TestAESProperty

I want to data bind from 'application-test.yml' file because it can be more secure with AWS service manager.


Solution

  • @ConfigurationProperties annotated classes can be tested using ApplicationContextRunner.

    Let's annotate key with @field:NotBlank. Please keep in mind that this is Spring Boot 2.7.18, hence javax instead of Jakarta.

    import org.springframework.boot.context.properties.ConfigurationProperties
    import org.springframework.boot.context.properties.ConstructorBinding
    import org.springframework.validation.annotation.Validated
    import javax.validation.constraints.NotBlank
    
    @Validated
    @ConstructorBinding
    @ConfigurationProperties(prefix = "common.test.aes")
    data class TestAESProperty(
        @field:NotBlank val key: String,
        val iv: String,
    )
    

    Now we can test that application context starts with valid value for key, and fails if it's blank. Values has to be defined with .withPropertyValues, not application-test.yml.

    import org.assertj.core.api.Assertions.assertThat
    import org.junit.jupiter.api.Assertions.assertEquals
    import org.springframework.boot.context.properties.EnableConfigurationProperties
    import org.springframework.boot.test.context.runner.ApplicationContextRunner
    import kotlin.test.Test
    
    class AESTest {
    
        @EnableConfigurationProperties(TestAESProperty::class)
        class TestConfig
    
        @Test
        fun `context load with valid properties`() {
            CONTEXT_RUNNER
                .withUserConfiguration(TestConfig::class.java)
                .withPropertyValues(
                    "common.test.aes.key=~key~",
                    "common.test.aes.iv=~iv~"
                )
                .run { context ->
                    assertThat(context).hasSingleBean(TestAESProperty::class.java)
    
                    val sut = context.getBean(TestAESProperty::class.java)
    
                    assertEquals("~key~", sut.key)
                    assertEquals("~iv~", sut.iv)
                }
        }
    
        @Test
        fun `context load with invalid properties`() {
            CONTEXT_RUNNER
                .withUserConfiguration(TestConfig::class.java)
                .withPropertyValues(
                    "common.test.aes.key=",
                    "common.test.aes.iv=~iv~"
                )
                .run { context ->
                    assertThat(context).hasFailed()
                }
        }
    
        companion object {
            val CONTEXT_RUNNER: ApplicationContextRunner = ApplicationContextRunner()
        }
    }