6

I am trying to use Neo4j TestContainers with Kotlin, Spring Data Neo4j, Spring Boot and JUnit 5. I have a lot of tests that require to use the test container. Ideally, I would like to avoid copying the container definition and configuration in each test class.

Currently I have something like:

@Testcontainers
@DataNeo4jTest
@Import(Neo4jConfiguration::class, Neo4jTestConfiguration::class)
class ContainerTest(@Autowired private val repository: XYZRepository) {

    companion object {
        const val IMAGE_NAME = "neo4j"
        const val TAG_NAME = "3.5.5"

        @Container
        @JvmStatic
        val databaseServer: KtNeo4jContainer = KtNeo4jContainer("$IMAGE_NAME:$TAG_NAME")
                .withoutAuthentication()
    }

    @TestConfiguration
    internal class Config {
        @Bean
        fun configuration(): Configuration = Configuration.Builder()
                .uri(databaseServer.getBoltUrl())
                .build()
    }

    @Test
    @DisplayName("Create xyz")
    fun testCreateXYZ() {
        // ...
    }

}

class KtNeo4jContainer(val imageName: String) : Neo4jContainer<KtNeo4jContainer>(imageName)

How can I extract the databaseServer definition and the @TestConfiguration? I tried different ways of creating a base class and having the ContainerTest extend it, but it is not working. From what I understand, static attriubutes are not inherited in Kotlin.

Martin L.
  • 135
  • 1
  • 7

3 Answers3

3

Below my solution for sharing same container between tests.

@Testcontainers
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
abstract class IntegrationTest {

companion object {
    @JvmStatic
    private  val mongoDBContainer = MongoDBContainer(DockerImageName.parse("mongo:4.0.10"))
        .waitingFor(HostPortWaitStrategy())

    @BeforeAll
    @JvmStatic
    fun beforeAll() {
        mongoDBContainer.start()
    }

    @JvmStatic
    @DynamicPropertySource
    fun registerDynamicProperties(registry: DynamicPropertyRegistry) {
        registry.add("spring.data.mongodb.host", mongoDBContainer::getHost)
        registry.add("spring.data.mongodb.port", mongoDBContainer::getFirstMappedPort)
    }
 }
}

The key here is to not use @Container annotation as it will close just created container after your first test subclass executes all tests. Method start() in beforeAll() initialize container only once (upon first subclass test execution), then does nothing while container is running.

By theory we shouldn't have to do this hack, based on: https://www.testcontainers.org/test_framework_integration/junit_5/

...container that is static should not be closed until all of tests of all subclasses are finished, but it's not working that way and I don't know why. Would be nice to have some answer on that :).

1

I've had the same issue (making Spring Boot + Kotlin + Testcontainers work together) and after searching the web for (quite) a while I found this nice solution: https://github.com/larmic/testcontainers-junit5. You'll just have to adopt it to your database.

Daniel Bimschas
  • 513
  • 1
  • 5
  • 10
0

I faced very similar issue in Kotlin and spring boot 2.4.0. The way you can reuse one testcontainer configuration can be achieved through initializers, e.g.: https://dev.to/silaev/the-testcontainers-mongodb-module-and-spring-data-mongodb-in-action-53ng or https://nirajsonawane.github.io/2019/12/25/Testcontainers-With-Spring-Boot-For-Integration-Testing/ (java versions) I wanted to use also new approach of having dynamicProperties and it worked out of a boxed in java. In Kotlin I made sth like this (I wasn't able to make @Testcontainer annotations working for some reason). It's not very elegant but pretty simple solution that worked for me:

MongoContainerConfig class:

import org.testcontainers.containers.MongoDBContainer

class MongoContainerConfig {
    companion object { 
        @JvmStatic
        val mongoDBContainer = MongoDBContainer("mongo:4.4.2")
    }

    init {
        mongoDBContainer.start()
    }
}

Test class:

@SpringBootTest(
    classes = [MongoContainerConfig::class]
)
internal class SomeTest {

    companion object {
        @JvmStatic
        @DynamicPropertySource
        fun setProperties(registry: DynamicPropertyRegistry) {
            registry.add("mongodb.uri") { 
                  MongoContainerConfig.mongoDBContainer.replicaSetUrl 
        }
    }
}

Disadvantage is this block with properties in every test class what suggests that maybe approach with initializers is desired here.