@DynamicPropertySource not being invoked (Kotlin, Spring Boot and TestContainers)
Asked Answered
T

4

8

I'm trying to define a @TestConfiguration class that is executed once before all integration tests to run a MongoDB TestContainer in Kotlin in a Spring Boot project.

Here is the code:

import org.springframework.boot.test.context.TestConfiguration
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.testcontainers.containers.MongoDBContainer
import org.testcontainers.utility.DockerImageName


@TestConfiguration
class TestContainerMongoConfig {

  companion object {

    @JvmStatic
    private val MONGO_CONTAINER: MongoDBContainer = MongoDBContainer(DockerImageName.parse("mongo").withTag("latest")).withReuse(true)

    @JvmStatic
    @DynamicPropertySource
    private fun emulatorProperties(registry: DynamicPropertyRegistry) {
      registry.add("spring.data.mongodb.uri", MONGO_CONTAINER::getReplicaSetUrl)
    }

    init { MONGO_CONTAINER.start() }

  }

}

The issue seems to be that emulatorProperties method is not being called. The regular flow should be that the container is started and then the properties are set. The first step happens, the second does not.

I know there is an alternative for which I can do this configuration in each functional test class but I don't like it as it adds not needed noise to the test class.

For example, with a Java project that uses Postgres I managed to make it work with the following code:

import javax.sql.DataSource;

import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.utility.DockerImageName;


@TestConfiguration
public class PostgresqlTestContainersConfig {

  static final PostgreSQLContainer POSTGRES_CONTAINER;
  private final static DockerImageName IMAGE = DockerImageName.parse("postgres").withTag("latest");

  static {
    POSTGRES_CONTAINER = new PostgreSQLContainer(IMAGE);
    POSTGRES_CONTAINER.start();
  }


  @Bean
  DataSource dataSource() {
    return DataSourceBuilder.create()
        .username(POSTGRES_CONTAINER.getUsername())
        .password(POSTGRES_CONTAINER.getPassword())
        .driverClassName(POSTGRES_CONTAINER.getDriverClassName())
        .url(POSTGRES_CONTAINER.getJdbcUrl())
        .build();
  }
}

I'm trying to achieve the same thing but in Kotlin and using MongoDB.

Any idea on what may be the issue causing the @DynamicPropertySource not being called?

Triplet answered 18/10, 2022 at 12:12 Comment(0)
A
5

@DynamicPropertySource is part of the Spring-Boot context lifecycle. Since you want to replicate the Java setup in a way, it is not required to use @DynamicPropertySource. Instead you can follow the Singleton Container Pattern, and replicate it in Kotlin as well.

Instead of setting the config on the registry, you can set them as a System property and Spring Autoconfig will pick it up:

    init { 
      MONGO_CONTAINER.start() 
      System.setProperty("spring.data.mongodb.uri", MONGO_CONTAINER.getReplicaSetUrl());
    }
Attica answered 19/10, 2022 at 7:36 Comment(0)
S
12

This solution works for me.
Method with @DynamicPropertySource is inside companion object(also added @JvmStatic) and added org.testcontainers.junit.jupiter.Testcontainers on the test class

import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import javax.sql.DataSource


@ExtendWith(SpringExtension::class)
@Testcontainers
@TestConfiguration
@ContextConfiguration(classes = [PostgresqlTestContainersConfig::class])
class PostgresqlTestContainersConfig {

    @Autowired
    var dataSource: DataSource? = null

    @Test
    internal fun name() {
        dataSource!!.connection.close()
    }

    @Bean
    fun dataSource(): DataSource? {
        return DataSourceBuilder.create()
            .username(POSTGRES_CONTAINER.getUsername())
            .password(POSTGRES_CONTAINER.getPassword())
            .driverClassName(POSTGRES_CONTAINER.getDriverClassName())
            .url(POSTGRES_CONTAINER.getJdbcUrl())
            .build()
    }

    companion object {

        @JvmStatic
        @Container
        private val POSTGRES_CONTAINER: PostgreSQLContainer<*> = PostgreSQLContainer("postgres:9.6.12")
            .withDatabaseName("integration-tests-db")
            .withUsername("sa")
            .withPassword("sa")

        @JvmStatic
        @DynamicPropertySource
        fun postgreSQLProperties(registry: DynamicPropertyRegistry) {
            registry.add("db.url") { POSTGRES_CONTAINER.jdbcUrl }
            registry.add("db.user") { POSTGRES_CONTAINER.username }
            registry.add("db.password") { POSTGRES_CONTAINER.password }
        }
    }

}
Snowinsummer answered 20/12, 2022 at 13:24 Comment(1)
adding @JvmStatic is required here.Santee
A
5

@DynamicPropertySource is part of the Spring-Boot context lifecycle. Since you want to replicate the Java setup in a way, it is not required to use @DynamicPropertySource. Instead you can follow the Singleton Container Pattern, and replicate it in Kotlin as well.

Instead of setting the config on the registry, you can set them as a System property and Spring Autoconfig will pick it up:

    init { 
      MONGO_CONTAINER.start() 
      System.setProperty("spring.data.mongodb.uri", MONGO_CONTAINER.getReplicaSetUrl());
    }
Attica answered 19/10, 2022 at 7:36 Comment(0)
C
1

I was able to resolve similar problem in Groovy by:

Having static method annotated with @DynamicPropetySource directly in the test class (probably it would also work in superclass.

But I didn't want to copy the code into every test class that needs MongoDB. I resolved the issue by using ApplicationContexInitializer

The example is written in groovy

class MongoTestContainer implements ApplicationContextInitializer<ConfigurableApplicationContext>{

  static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:6.0.2"))

  @Override
  void initialize(ConfigurableApplicationContext applicationContext) {
    mongoDBContainer.start()
    def testValues = TestPropertyValues.of("spring.data.mongodb.uri="+ mongoDBContainer.getReplicaSetUrl())
    testValues.applyTo(applicationContext.getEnvironment())
  }
}

To make it complete, in the test class, you just need to add @ContextConfiguration(initializers = MongoTestContainer) to activate context initializer for the test.

For this you could also create custom annotation which would combine @DataMongoTest with previous annotation.

Cambrian answered 19/10, 2022 at 11:54 Comment(0)
R
0

This is how you can do it using Tsst Containers which is where I use @DynamicPropertySource

@DataJpaTest
@EnableJpaRepositories
@EntityScan
@Testcontainers
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyContainerTest {

  companion object {
    @Container
    private val MYSQL = MySQLContainer()

    @JvmStatic
    @DynamicPropertySource
    fun dataSourceProperties(registry: DynamicPropertyRegistry) {
      registry.add("spring.datasource.url") { MYSQL.jdbcUrl }
      registry.add("spring.datasource.username") { MYSQL.username }
      registry.add("spring.datasource.password") { MYSQL.password }
    }
  }

  @Autowired
  private lateinit var transactionTemplate : TransactionTemplate

  @Test
  fun `in-container example`() {
    val result = transactionTemplate.execute {
      ...
    }
    assertThat(result)
      .isNotNull
  }

}
Reedreedbird answered 9/4 at 0:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.