Java 带testcontainers的Spring引导-如何防止在重新加载上下文时初始化DB 上下文
我在Spring boot应用程序中有一套集成测试。测试上下文使用MSSQL docker容器作为使用框架的数据库 我的一些测试使用Mockito和SpyBean,这将重新启动Spring上下文,因为spied bean不能在测试之间共享 由于我使用的是在所有测试期间都有效的非嵌入式数据库,因此在开始时通过使用以下命令执行my schema.sql和data.sql来配置数据库:-Java 带testcontainers的Spring引导-如何防止在重新加载上下文时初始化DB 上下文,java,spring,spring-boot,spring-mvc,testcontainers,Java,Spring,Spring Boot,Spring Mvc,Testcontainers,我在Spring boot应用程序中有一套集成测试。测试上下文使用MSSQL docker容器作为使用框架的数据库 我的一些测试使用Mockito和SpyBean,这将重新启动Spring上下文,因为spied bean不能在测试之间共享 由于我使用的是在所有测试期间都有效的非嵌入式数据库,因此在开始时通过使用以下命令执行my schema.sql和data.sql来配置数据库:- spring.datasource.initialization-mode=always 问题是当Spring上
spring.datasource.initialization-mode=always
问题是当Spring上下文重新启动时,我的数据库再次初始化,这会触发错误,例如唯一约束问题、表已存在等。
如果有任何帮助,我的父测试类如下所示:-
@ActiveProfiles(Profiles.PROFILE_TEST)
@Testcontainers
@SpringJUnitWebConfig
@AutoConfigureMockMvc
@SpringBootTest(classes = Application.class)
@ContextConfiguration(initializers = {IntegrationTest.Initializer.class})
public abstract class IntegrationTest {
private static final MSSQLServerContainer<?> mssqlContainer;
static {
mssqlContainer = new MSSQLServerContainer<>()
.withInitScript("setup.sql"); //Creates users/permissions etc
mssqlContainer.start();
}
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(final ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of("spring.datasource.url=" + mssqlContainer.getJdbcUrl())
.applyTo(configurableApplicationContext.getEnvironment());
}
}
}
但是,据我所知,它没有:(
可能但不完整的解决方案
@Component
public static class EventListener implements ApplicationListener<ApplicationEvent> {
@Autowired
private ConfigurableEnvironment environment;
@Override
public void onApplicationEvent(final ApplicationEvent event) {
log.info(event.getClass().getSimpleName());
if (event instanceof ContextRefreshedEvent) {
TestPropertyValues.of("spring.datasource.initialization-mode=never")
.applyTo(this.environment);
}
}
}
@组件
公共静态类EventListener实现ApplicationListener{
@自动连线
私人可配置环境;
@凌驾
ApplicationEvent上的公共无效(最终ApplicationEvent事件){
log.info(event.getClass().getSimpleName());
if(ContextRefreshedEvent的事件实例){
TestPropertyValues.of(“spring.datasource.initialization mode=never”)
.applyTo(本环境);
}
}
}
我的猜测是,当上下文重新启动时,它还会重新加载所有原始属性源(mode=always)。在加载属性之后和架构创建之前,我需要一个事件
那么,有人有什么建议吗?所以我最终找到了一个解决方法。感觉有点不对劲,但除非其他人能够建议一个更合适、更不晦涩的解决方案,否则这就是我要做的 该解决方案使用原子布尔的@tsarenkotxt建议和my#3部分解决方案的组合
@ActiveProfiles(Profiles.PROFILE_TEST)
@Testcontainers
@SpringJUnitWebConfig
@AutoConfigureMockMvc
@SpringBootTest(classes = Application.class)
@ContextConfiguration(initializers = {IntegrationTest.Initializer.class})
public abstract class IntegrationTest {
private static final MSSQLServerContainer mssqlContainer;
//added this
private static final AtomicBoolean initDB = new AtomicBoolean(true);
static {
mssqlContainer = new MSSQLServerContainer()
.withInitScript("setup.sql"); //Creates users/permissions etc
mssqlContainer.start();
}
static class Initializer implements ApplicationContextInitializer {
@Override
public void initialize(final ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of(
"spring.datasource.url=" + mssqlContainer.getJdbcUrl(),
//added this
"spring.datasource.initialization-mode=" + (initDB.get() ? "always" : "never"))
.applyTo(configurableApplicationContext.getEnvironment());
//added this
initDB.set(false);
}
}
}
@ActiveProfiles(Profiles.PROFILE\u测试)
@测试容器
@SpringJUnitWebConfig
@AutoConfigureMockMvc
@SpringBootTest(类=Application.class)
@ContextConfiguration(初始值设定项={IntegrationTest.Initializer.class})
公共抽象类集成测试{
私有静态最终MSSQLServerContainer MSSQLServerContainer;
//加上这个
private static final AtomicBoolean initDB=新的AtomicBoolean(true);
静止的{
MSSQLServerContainer=新的MSSQLServerContainer()
.withInitScript(“setup.sql”);//创建用户/权限等
mssqlContainer.start();
}
静态类初始值设定项实现ApplicationContextInitializer{
@凌驾
公共无效初始化(最终配置Application Context配置Application Context){
TestPropertyValue.of(
“spring.datasource.url=“+mssqlContainer.getJdbcUrl(),
//加上这个
“spring.datasource.initialization mode=“+(initDB.get()?“始终”:“从不”))
.applyTo(configurableApplicationContext.getEnvironment());
//加上这个
initDB.set(false);
}
}
}
基本上,我在第一次启动时将spring.datasource.initialization模式设置为始终,因为数据库尚未设置,然后在此后的每次上下文初始化时将其重置为从不。因此,spring在第一次运行后不会尝试执行启动脚本
工作很好,但我不喜欢将此配置隐藏在这里,因此仍然希望其他人能够“设计”出更好、更多的东西。您可以尝试使用
ResourceDatabasePopulator
->addScripts()
->execute()为您的测试实现自定义once
功能
,如果它对您有效,还可以查看一下DataSourceInitializePostProcessor
@tsarenkotxt,这很有趣,但我的理解是,我要声明这是一个bean,这意味着它将被重新创建(并因此重新执行)在上下文重新加载时不是吗?为了跟踪计数器,我实际上需要引用一个在上下文重新加载后仍然存在的状态,或者您有其他想法吗?是的,这个bean将被重新创建。是的,要将状态存储在静态原子布尔
中,可能这不是最好的方法,但它y的测试工作SI将转移到flyway。flyway将检查元数据表,查看需要应用哪个版本的脚本。如果DB是持久的,flyway将不会重新应用现有版本。@MartinFrey提出了很好的建议,这肯定是我们正在进行的支持flyway或Liquibase的工作,但需要更多努力实现和支持。但这并不能直接解决我面临的问题,即阻止在Springboottest启动时重新提供非嵌入式DB;)
spring.datasource.continue-on-error=true
@Component
public static class EventListener implements ApplicationListener<ApplicationEvent> {
@Autowired
private ConfigurableEnvironment environment;
@Override
public void onApplicationEvent(final ApplicationEvent event) {
log.info(event.getClass().getSimpleName());
if (event instanceof ContextRefreshedEvent) {
TestPropertyValues.of("spring.datasource.initialization-mode=never")
.applyTo(this.environment);
}
}
}
@ActiveProfiles(Profiles.PROFILE_TEST)
@Testcontainers
@SpringJUnitWebConfig
@AutoConfigureMockMvc
@SpringBootTest(classes = Application.class)
@ContextConfiguration(initializers = {IntegrationTest.Initializer.class})
public abstract class IntegrationTest {
private static final MSSQLServerContainer mssqlContainer;
//added this
private static final AtomicBoolean initDB = new AtomicBoolean(true);
static {
mssqlContainer = new MSSQLServerContainer()
.withInitScript("setup.sql"); //Creates users/permissions etc
mssqlContainer.start();
}
static class Initializer implements ApplicationContextInitializer {
@Override
public void initialize(final ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of(
"spring.datasource.url=" + mssqlContainer.getJdbcUrl(),
//added this
"spring.datasource.initialization-mode=" + (initDB.get() ? "always" : "never"))
.applyTo(configurableApplicationContext.getEnvironment());
//added this
initDB.set(false);
}
}
}