Skip to main content

Command Palette

Search for a command to run...

Local development with Testcontainers, Kotlin and Spring Boot

Updated
T

Thomas Schühly’s server-side rendering journey started as a developer trying to make life easier while developing his first bootstrapped product in his free time. Creating Spring ViewComponent enabled him to be the youngest Speaker at the largest European Spring conference and build awesome software full-time with his open-source library at alanda.io. He regularly talks at Java User Groups about htmx and server-side rendering with Spring while contributing to the open-source community. PhotoQuest

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",
    )
}

If you want to learn more about HTMX + Spring Boot check out my series Web development without the JavaScript headache with Spring + HTMX.

My side business PhotoQuest is also built with HTMX + JTE

More from this blog

Thomas Schilling | Spring/HTMX/Claude Code

22 posts

Youngest Speaker @Spring I/O & Spring ViewComponent creator.

Passionate about building awesome software with Spring + HTMX. Pushing full-stack development with Spring forward.