spring 发生事件后如何使用自定义参数启动@Bean 进行测试?
Posted
技术标签:
【中文标题】spring 发生事件后如何使用自定义参数启动@Bean 进行测试?【英文标题】:How to start a @Bean with custom parameters after an event had happened with spring for tests? 【发布时间】:2021-11-26 19:59:52 【问题描述】:我正在为使用 R2dbc 的项目添加带有 TestContainers 框架的 RepositoryTests,我遇到了以下情况:
1 - 在主项目中,我在 application.yaml 文件中设置了 r2dbc
url(带有端口和主机名),spring 数据管理一切,一切正常。
2 - 然而,在测试中,我使用的是 TestContainers 框架,更具体地说是 DockerComposeContainer
,我用它来使用 docker-compose.test.yaml
文件和我需要的数据库创建一个模拟容器。
3 - 这个容器在旅途中创建了一个port
编号我在我的 docker-compose 文件中定义了一个端口号,但是DockerComposeContainer
将提供给我的端口号是随机的,并且每次我运行测试时都会发生变化,这使得在application-test.yaml
上拥有静态网址不再是一个选项。
所以我需要在运行时动态地创建这个bean 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()
)
这就是我的数据库启动器的样子:
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))
尝试启动此模板工厂时没有收到任何有用的错误消息, 但老实说,我不知道我是否正在努力寻找正确的解决方案,有没有人知道如何解决这个问题,或者我在这里做错了什么?
所以总结一下生产应用程序很好,它从 application.yaml 文件上的 url 开始,就是这样,但对于测试,我需要一些动态的端口,每次都会改变。
提前谢谢你))
【问题讨论】:
【参考方案1】:Spring 已经为您的问题提供了解决方案。
如果您使用的是最新的 Spring 版本 (>= 5.2.5
),您应该使用 @DynamicPropertySource
以便使用容器数据库端口的动态值调整测试配置属性。阅读official spring documentation 了解更多详细信息和 kotlin 代码示例。
如果您使用旧的 Spring 版本,您需要的接口是ApplicationContextInitializer
。请参阅此spring github issue 以获取一个小示例。
【讨论】:
以上是关于spring 发生事件后如何使用自定义参数启动@Bean 进行测试?的主要内容,如果未能解决你的问题,请参考以下文章
带有自定义 ReactiveAuthenticationManager 的 Spring 启动执行器审计事件