Search code examples
spring-bootkotlinjooqspring-boot-testtestcontainers

Spring boot with testcontainers and jOOQ doesn't inject DSL context


I have some problem with spring boot + jOOQ and testcontainers. DSL context won't inject in my test class.

I made some preparations for using SQLContainer

class SpringTestContainer: PostgreSQLContainer<SpringTestContainer> {

    private val postgreSqlPort = 5432
    private val db = "m4"

    companion object {
        var instance: SpringTestContainer? = null

        fun get(): SpringTestContainer {
            if(instance == null) {
                instance = SpringTestContainer()
            }

            return instance!!
        }
    }

    override fun getDatabaseName(): String = db

    constructor() : this("registry.dev.tskad.stdev.ru/m4/db:latest")

    constructor(dockerImageName: String) : super(dockerImageName){
        withImagePullPolicy(PullPolicy.alwaysPull())
        addExposedPort(postgreSqlPort)
        waitStrategy = LogMessageWaitStrategy()
            .withRegEx(".*database system is ready to accept connections.*\\s")
            .withTimes(1)
            .withStartupTimeout(Duration.of(30, ChronoUnit.SECONDS))
    }

    override fun getJdbcUrl(): String {
        return String.format("jdbc:postgresql://%s:%d/%s", containerIpAddress, getMappedPort(postgreSqlPort), databaseName)
    }

    override fun waitUntilContainerStarted() {
        getWaitStrategy().waitUntilReady(this)
    }

    override fun getLivenessCheckPorts(): Set<Int?> {
        return HashSet(getMappedPort(postgreSqlPort))
    }

}

Then I created some abstraction for extending my integration test classes

@ContextConfiguration(initializers = [SpringIntegrationTest.Initializer::class])
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@JooqTest
abstract class SpringIntegrationTest {

    @get:Rule
    var postgreSQLContainer = SpringTestContainer.get()

    inner class Initializer: ApplicationContextInitializer<ConfigurableApplicationContext> {
        override fun initialize(applicationContext: ConfigurableApplicationContext) {
            with(applicationContext.environment.systemProperties) {
                put("spring.datasource.url", postgreSQLContainer.jdbcUrl)
                put("spring.datasource.username", postgreSQLContainer.username)
                put("spring.datasource.password", postgreSQLContainer.password)
            }
        }
    }

}

and then I implemented test class

@ExtendWith(SpringExtension::class)
class TransactionRepositoryImplTest: SpringIntegrationTest() {

    @Autowired
    private var dslContext: DSLContext? = null

    private var transactionRepository: TransactionRepository? = null

    @Before
    fun setUp() {
        assertThat(dslContext).isNotNull
        transactionRepository = TransactionRepositoryImpl(dslContext!!)
    }

    @After
    fun tearDown() {

    }

    @Test
    fun findTransactionData() {
        transactionRepository?.findTransactionByVehicleUuid(null).apply {
            assertNull(this)
        }

    }

}

and when I started tests of this class - tests are fails, because of assertions are not passed. Here is the report of tests https://pastebin.com/0HeqDcCT

So.. how it impossible? I saw a few guides with this stack(Spring/jOOQ/TestContainers). And they are all working. Maybe I missed some test dependencies? If you have experience with this case - share your solution, please. I will be very grateful.

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    implementation("org.springframework.boot:spring-boot-starter-jooq")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("org.springframework.cloud:spring-cloud-starter-consul-config")
    implementation("org.springframework.cloud:spring-cloud-stream")
    implementation("org.springframework.cloud:spring-cloud-stream-binder-kafka")
    implementation("org.springframework.kafka:spring-kafka")
    implementation("org.springframework.boot:spring-boot-starter-amqp")
    implementation ("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.10.3")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.10.3")
    runtimeOnly("org.postgresql:postgresql:42.2.12")
    jooqGeneratorRuntime("org.postgresql:postgresql:42.2.12")
    annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
    testImplementation("org.springframework.boot:spring-boot-starter-test") {
        exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
        exclude(module = "junit")
    }
    testImplementation("com.ninja-squad:springmockk:2.0.1")
    testImplementation("org.springframework.cloud:spring-cloud-stream-test-support")
    testImplementation("org.springframework.kafka:spring-kafka-test")
    testImplementation("org.springframework.amqp:spring-rabbit-test")
    testImplementation("org.testcontainers:postgresql:1.14.3")
}

Solution

  • I found the solution. The right way was to override the start method of test containers implementation and put in system properties the credentials to container DB.

    Here is the working code:

    class SpringTestContainer: PostgreSQLContainer<SpringTestContainer> {
    
        private val postgreSqlPort = 5432
        private val db = "m4"
    
        companion object {
            var instance: SpringTestContainer? = null
    
            fun get(): SpringTestContainer {
                if(instance == null) {
                    instance = SpringTestContainer()
                }
    
                return instance!!
            }
        }
    
        override fun getDatabaseName(): String = db
    
        constructor() : this("registry.dev.tskad.stdev.ru/m4/db:latest")
    
        constructor(dockerImageName: String) : super(dockerImageName){
            withImagePullPolicy(PullPolicy.alwaysPull())
            addExposedPort(postgreSqlPort)
            waitStrategy = LogMessageWaitStrategy()
                .withRegEx(".*database system is ready to accept connections.*\\s")
                .withTimes(1)
                .withStartupTimeout(Duration.of(30, ChronoUnit.SECONDS))
        }
    
        override fun getJdbcUrl(): String {
            return String.format("jdbc:postgresql://%s:%d/%s", containerIpAddress, getMappedPort(postgreSqlPort), databaseName)
        }
    
        override fun waitUntilContainerStarted() {
            getWaitStrategy().waitUntilReady(this)
        }
    
        override fun getLivenessCheckPorts(): Set<Int?> {
            return HashSet(getMappedPort(postgreSqlPort))
        }
    
        override fun start() {
            super.start()
            val container = get()
            System.setProperty("DB_URL", container.jdbcUrl)
            System.setProperty("DB_USERNAME", container.username)
            System.setProperty("DB_PASSWORD", container.password)
        }
    }
    
    @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
    @SpringBootTest(properties = ["spring.cloud.consul.enabled = false"])
    @EnableAutoConfiguration(exclude = [
        RabbitAutoConfiguration::class,
        KafkaAutoConfiguration::class
    ])
    class TransactionRepositoryImplTest {
    
        @get:Rule
        var postgreSQLContainer = SpringTestContainer.get()
    
        @Autowired
        private lateinit var dslContext: DSLContext
    
        @Autowired
        private lateinit var transactionRepository: TransactionRepository
    
        @MockkBean
        private lateinit var connectionFactory: ConnectionFactory // this is the mock for rabbit connection. U may ignore it.
    
        @Test
        fun contextLoads() {
            Assertions.assertNotNull(dslContext)
            Assertions.assertNotNull(transactionRepository)
        }
    
    }
    

    and then need to fix application.yml in the tests directory

    spring:
      datasource:
        platform: postgres
        url: ${DB_URL}
        username: ${DB_USERNAME}
        password: ${DB_PASSWORD}
        driverClassName: org.postgresql.Driver