Java 带testcontainers的Spring引导-如何防止在重新加载上下文时初始化DB 上下文

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 boot应用程序中有一套集成测试。测试上下文使用MSSQL docker容器作为使用框架的数据库

我的一些测试使用Mockito和SpyBean,这将重新启动Spring上下文,因为spied bean不能在测试之间共享

由于我使用的是在所有测试期间都有效的非嵌入式数据库,因此在开始时通过使用以下命令执行my schema.sql和data.sql来配置数据库:-

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());
        }
    }
}
但是,据我所知,它没有:(

可能但不完整的解决方案
  • 测试容器初始化脚本
  • 这在模式中的启动错误被忽略的意义上起作用。但是..如果有人在脚本中添加了一些不可靠的SQL,我希望它在启动时失败

  • 春季事件挂钩
  • 我的想法是,我可以监听ContextRefreshedEvent,然后为spring.datasource.initialization mode=never注入一个新值

    这有点像黑客,但我尝试了以下方法

        @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);
                }
            }
        }