# Kotest + Spring + Testcontainers

Recently, I wanted to rewrite a JUnit integration test for my Spring Boot application. Since it was an integration test, I used Testcontainers to start a database. While there are Kotest extensions for [Spring](https://kotest.io/docs/extensions/spring.html) and [Testcontainers](https://kotest.io/docs/extensions/test_containers.html) we have the problem that both do not cooperate.

The Testcontainers extension does not integrate with Spring in a way that ensures the container starts early enough for Spring and overrides the Spring properties (e.g., `spring.data.jdbc.url`).

While searching for a solution, I came across several articles that seemed somewhat outdated. Therefore, I wanted to share my solution here. The articles I found were:

* [Bootstrap project with Spring Boot, Kotest, Testcontainers & MongoDB | Jakub Prądzyński's Blog](https://jakubpradzynski.pl/posts/example-spring-kotest-testcontainers-mongodb-project/)
    
* [Using Testcontainers with Micronaut and Kotest | @akobor](https://akobor.me/posts/using-testcontainers-with-micronaut-and-kotest)
    
* [TestContainers Extention and Spring boot startup timing · Issue #1649 · kotest/kotest](https://github.com/kotest/kotest/issues/1649)
    

What all of those solutions have in common is that they wrap the container and then control its lifecycle. This could be achieved through extension functions, static methods, or an abstract test superclass. Having all the lifecycle callbacks, you can then start and stop the container before the Spring application context starts and override the properties.

Although these approaches work, they seemed unnecessarily complex to me.

## Starting point

What I started with was a common Spring JUnit test with Testcontainers:

```kotlin
@ActiveProfiles("default", "test")
@Testcontainers
@SpringBootTest
class SomeIntegrationTest() {
    companion object {
        @Container
        private val postgres = PostgreSQLContainer("postgres:16.1")

        @JvmStatic
        @DynamicPropertySource
        fun properties(registry: DynamicPropertyRegistry) {
            with(registry) {
                add("spring.flyway.url") { postgres.jdbcUrl }
                add("spring.flyway.user") { postgres.username }
                add("spring.flyway.password") { postgres.password }
                add("spring.r2dbc.url") {
                    postgres.jdbcUrl.replaceFirst("jdbc", "r2dbc")
                }
                add("spring.r2dbc.username") { postgres.username }
                add("spring.r2dbc.password") { postgres.password }
            }
        }
    }
// setup, teardown and tests go here
...
} 
```

While I could keep the `@SpringBootTest` annotation due to the Kotest extension `@Testcontainers` , which helps with the container lifecycle, does not work with Kotest.

## The Kotest version

What my predecessors did not have at their time was the `@ServiceConnection` annotation. You can read more about it [here](https://spring.io/blog/2023/06/23/improved-testcontainers-support-in-spring-boot-3-1). Given a standard Postgres container, it should be possible to use this annotation. So, I experimented and rewrote the test like this:

```kotlin
import io.kotest.extensions.testcontainers.perSpec

@ActiveProfiles("default", "test")
@SpringBootTest
class SomeIntegrationTest : StringSpec() {

    companion object {
        @ServiceConnection
        private val postgres = PostgreSQLContainer("postgres:16.1")
            .apply { this.start() }
    }

    init {
        listener(postgres.perSpec())
        // setup, teardown and tests go here
        ...
    }
} 
```

I also encountered the issue where the container started too late for Spring. To address this, I start the container explicitly in the companion object. Registering the container as a listener using `perSpec()` then managed the rest of the lifecycle.

With `spring-boot-testcontainers` in the classpath, there’s no need to override properties manually anymore. We can just add the `@ServiceConnection` annotation.

## Conclusion

So, all in all, instead of having to write our own lifecycle wrapper we just use `perSpec()` and the manual start with `.apply { this.start() }` and have a nicely working integration test. The `@ServiceConnection` annotation could also have been applied to the original test, but it’s great to see that it works seamlessly with the Kotest Spring extension.
