skip to Main Content

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?

3

Answers


  1. @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());
        }
    
    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
  3. 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 }
            }
        }
    
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search