Local development with Testcontainers, Kotlin and Spring Boot

It is always the best to keep your development environment as close as you can to your production environment. Don't use a h2 database as it can behave differently to a production database: Issues mapping @Lob

Recently I found the article Local development with Testcontainers by Sergei Egorov @bsideup.

I couldn't get it working with Kotlin, but after some help from the awesome twitter developer community I got it working.

Refactor your Application.kt, so you expose createSpringApplication as a static method:

@SpringBootApplication
class ExampleApplication{
    companion object {
        @JvmStatic
        fun createSpringApplication(): SpringApplication {
            return SpringApplication(ExampleApplication::class.java)
        }
    }

}

fun main(args: Array<String>) {
    runApplication<ExampleApplication>(*args)
}

Create a DevelopmentInitializer.kt in src/test/kotlin/com.example/DevelopmentInitializer.kt

@RunWith(SpringRunner::class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(initializers = [DevelopmentInitializer.Initializer::class])
abstract class DevelopmentInitializer {
    class Initializer : ApplicationContextInitializer<ConfigurableApplicationContext> {
        override fun initialize(context: ConfigurableApplicationContext) {
            val env = context.environment
            env.propertySources.addFirst(
                MapPropertySource(
                    "testcontainers", properties
                )
            )
        }

        companion object {
            private val postgresContainer = PostgreSQLContainer("postgres:14.4")
                .withUsername("postgres")
                .withPassword("password")
                .withDatabaseName("postgres")
                .withExposedPorts(5432)
                .withReuse(true)
            val properties: Map<String, String>
                get() {
                    Startables.deepStart(postgresContainer).join()
                    return mapOf(
                        "spring.datasource.url" to postgresContainer.jdbcUrl,
                        "spring.datasource.password" to postgresContainer.password,
                        "spring.datasource.username" to postgresContainer.username,
                    )
                }
        }
    }
}

To create reusable containers you need to set testcontainers.reuse.enable=true in $HOME/.testcontainers.properties file

Now create a DevelopmentApplication.kt in src/test/kotlin/com.example/DevelopmentApplication.kt

fun main(args: Array<String>) {
    val app = ExampleApplication.createSpringApplication()
    app.addInitializers(DevelopmentInitializer.Initializer())
    app.run(*args)
}

You can start DevelopmentApplication.main() to get your Spring Boot Application with an autoconfigured postgres container.

I created a working example at my GitHub: github.com/tschuehly/testcontainers-localdev-kotlin

Fixed Ports

You can also use a FixedHostPortContainer, then you can connect for example your IntelliJ Database Explorer and don't have to reconfigure it if you restart your Docker Environment

val postgresContainer = FixedHostPortGenericContainer("postgres:14.4")
    .withEnv("POSTGRES_USER","postgres")
    .withEnv("POSTGRES_PASSWORD","password")
    .withEnv("POSTGRES_DB","postgres")
    .withFixedExposedPort(5432,5432)
    .withReuse(true)

val properties: Map<String, String>
    get(){
        Startables.deepStart(postgresContainer, minioContainer).join()
        return mapOf(
            "spring.datasource.url" to 
                    "jdbc:postgresql://" + postgresContainer.host + ":5432/postgres",
            "spring.datasource.password" to "password",
            "spring.datasource.username" to "postgres",
    )
}