#kotlin #javabeans #applicationcontext #testcontainers
Вопрос:
Я работаю над добавлением тестов репозиториев с помощью платформы TestContainers для проекта, в котором используется R2dbc, и я сталкиваюсь со следующей ситуацией:
1 — В основном проекте я установил r2dbc
URL-адрес (с портом и именем хоста) в приложении.файл yaml и данные spring управляют всем, и все просто работает.
2 — Однако в тестах я использую платформу TestContainers, более конкретно DockerComposeContainer
, которую я использую для создания поддельного контейнера с помощью compose.test.yaml
файла docker с необходимыми мне базами данных.
3 — Этот контейнер создает port
номер на ходу Я определяю номер порта в своем файле docker-compose, но номер порта, который DockerComposeContainer
мне будет предоставлен, является случайным и меняется каждый раз, когда я запускаю тесты, что делает статический URL application-test.yaml
-адрес больше не вариантом.
Поэтому мне нужно динамически создать этот компонент R2dbcEntityTemplate
во время выполнения и только после DockerComposeContainer
того, как он сообщит мне номер порта. Таким образом, мое приложение может подключиться к правильному порту, и все должно работать так, как ожидалось.
Я пытался создать этот класс:
package com.wayfair.samworkgroupsservice.adapter
import io.r2dbc.mssql.MssqlConnectionConfiguration
import io.r2dbc.mssql.MssqlConnectionFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.config.ConstructorArgumentValues
import org.springframework.beans.factory.support.BeanDefinitionRegistry
import org.springframework.beans.factory.support.GenericBeanDefinition
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Profile
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate
import org.springframework.data.r2dbc.dialect.SqlServerDialect
import org.springframework.r2dbc.core.DatabaseClient
import org.springframework.stereotype.Component
@Component
@Profile("test")
class TemplateFactory(
@Autowired val applicationContext: ApplicationContext
) {
private val beanFactory = applicationContext.autowireCapableBeanFactory as BeanDefinitionRegistry
fun registerTemplateBean(host: String, port: Int) {
val beanDefinition = GenericBeanDefinition()
beanDefinition.beanClass = R2dbcEntityTemplate::class.java
val args = ConstructorArgumentValues()
args.addIndexedArgumentValue(
0,
DatabaseClient.builder()
.connectionFactory(connectionFactory(host, port))
.bindMarkers(SqlServerDialect.INSTANCE.bindMarkersFactory)
.build()
)
args.addIndexedArgumentValue(1, DefaultReactiveDataAccessStrategy(SqlServerDialect.INSTANCE))
beanDefinition.constructorArgumentValues = args
beanFactory.registerBeanDefinition("R2dbcEntityTemplate", beanDefinition)
}
// fun entityTemplate(host: String = "localhost", port: Int = 1435) =
// R2dbcEntityTemplate(
// DatabaseClient.builder()
// .connectionFactory(connectionFactory(host, port))
// .bindMarkers(SqlServerDialect.INSTANCE.bindMarkersFactory)
// .build(),
// DefaultReactiveDataAccessStrategy(SqlServerDialect.INSTANCE)
// )
private fun connectionFactory(host: String, port: Int) =
MssqlConnectionFactory(
MssqlConnectionConfiguration.builder()
.host(host)
.port(port)
.username("sa")
.password("Password123@#?")
.build()
)
}
And this is how my db initiliser looks like:
package com.wayfair.samworkgroupsservice.adapter.note
import com.wayfair.samworkgroupsservice.adapter.DBInitializerInterface
import com.wayfair.samworkgroupsservice.adapter.TemplateFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate
import org.testcontainers.containers.DockerComposeContainer
import org.testcontainers.containers.wait.strategy.Wait
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import java.io.File
@Testcontainers
class NoteTagDBInitializer : DBInitializerInterface {
@Autowired
override lateinit var client: R2dbcEntityTemplate
@Autowired
lateinit var factory: TemplateFactory
override val sqlScripts = listOf(
"db/note/schema.sql",
"db/note/reset.sql",
"db/note/data.sql"
)
init {
factory.registerTemplateBean(
cont.getServiceHost("test-db-local_1", 1433),
cont.getServicePort("test-db-local_1", 1433)
)
}
companion object {
@Container
val cont: KDockerComposerContainer = KDockerComposerContainer("docker-compose.test.yml")
.withExposedService(
"test-db-local_1", 1433,
Wait.forListeningPort()
)
.withLocalCompose(true)
.also {
it.start()
val porttt = it.getServicePort("test-db-local_1", 1433)
print(porttt)
}
class KDockerComposerContainer(yamlFile: String) :
DockerComposeContainer<KDockerComposerContainer>(File(yamlFile))
}
}
Я не получаю ошибок при попытке запустить эту фабрику шаблонов без полезного сообщения об ошибке,
Но, честно говоря, я больше не знаю, прилагаю ли я усилия для правильного решения, есть ли у кого-нибудь какое-либо представление о том, как это сделать, или я делаю здесь что-то не так?
Итак, чтобы подвести итог для производственного приложения, все в порядке, оно запускается на основе URL-адреса приложения.файл yaml и все, но для тестов мне нужно что-то динамичное с портами, которые будут меняться каждый раз.
Заранее спасибо ))
Ответ №1:
У Spring уже есть решение вашей проблемы. Если вы используете довольно свежую версию Spring (>= > 5.2.5
), вам следует использовать @DynamicPropertySource
для настройки свойств конфигурации теста динамическое значение порта базы данных контейнера. Прочитайте официальную документацию spring для получения более подробной информации и примеров кода kotlin.
Если вы застряли со старой версией Spring, вам нужен интерфейс ApplicationContextInitializer
. Смотрите этот весенний выпуск github для небольшого примера.