Spring @Sql Annotations,可以在所有测试之前运行一次吗? JDBCR2DBC

问题描述

使用 Spring 进行集成测试我能够填充一个测试数据库运行脚本,就像这样......

@Test
@sql({"/db/schema.sql","/db/accountConfig.sql","/db/functions/fnSomething.sql"})
public void verifySomething() {
   ...
}

但是,在任何测试运行之前,我只想运行我所有的 .sql 文件一次。有没有 JUnit 4 的方法来做到这一点?似乎 @sql 只针对带有 @Test 注释的方法运行。

我使用的是 Junit 4、Spring Boot、Java 15、Testcontainers。

我尝试过的东西...

  • 我曾尝试在我的测试类扩展的类上使用 @BeforeClass,但它似乎在我的测试之后运行。
  • Testcontainers 确实有一个 init 脚本函数,但它只需要一个文件,并不理想。
  • 我也尝试过 ScriptUtils.executesqlScript,但出于某种原因,测试容器不喜欢那样。

这是我的示例代码,适用于 @sql 但不适用于 ScriptUtils.executesqlScript

@ContextConfiguration(initializers = AbstractIntegrationTest.Initializer.class)
public abstract class AbstractIntegrationTest {

    @ClassRule 
    public static MSsqlServerContainer mssqlserver = new MSsqlServerContainer();

  
    public static class Initializer implements ApplicationContextinitializer<ConfigurableApplicationContext> {

        @Override
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
            ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
            Properties props = new Properties();
            props.put("spring.datasource.driver-class-name",mssqlserver.getDriverClassName());
            props.put("spring.datasource.url",mssqlserver.getJdbcUrl());
            props.put("spring.datasource.username",mssqlserver.getUsername());
            props.put("spring.datasource.password",mssqlserver.getpassword());

            environment
                .getPropertySources()
                .addFirst(new PropertiesPropertySource("myTestDBProps",props));      

            configurableApplicationContext.setEnvironment(environment);
    }
}

我的测试类只是扩展了 AbstractIntegrationTest。但是使用 @sql 会为每个测试用例运行脚本。有没有人有更好的方法来初始化 sql 脚本的建议?尝试过 flyway,但它不允许从脚本创建数据库

解决方法

我建议查看在 Spring 上下文初始化时执行一次 SQL 脚本的数据库初始化 bean。基本上,根据您使用的是 JDBC 还是 R2DBC,有两种解决方案。由于您要初始化多个脚本,您应该使用 CompositeDatabasePopulator。还要记住导入正确的类,因为它们具有相同的名称但来自不同的包,同样取决于 JDBC/R2DBC。

要从 resources 文件夹加载资源,请随意使用以下任一选项:

  • Resource resource = new ClassPathResource("sql/schema.sql"))
  • @Value("classpath:sql/schema.sql") Resource resource

此解决方案相当灵活,因为您可以使用 @TestConfiguration 为测试上下文定义初始化 bean(请记住使用此批注有点棘手,因此我建议您参阅这篇文章:Quirks of Spring's @TestConfiguration我很多)。

该解决方案应该适用于任何具有 ConnectionFactory 可用的解决方案,包括 Test Containers

JDBC

通过调用 populate 的初始化必须发生在 @PostConstuct 中,因为 Spring Boot 不会自动检测填充器。我建议将以下代码片段包装在配置类中,并将其包含在测试范围内。

@Autowired
private DataSource dataSource;

@PostConstruct
public void initData() throws SQLException {

    var populator = new CompositeDatabasePopulator();
    populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("sql/schema.sql")));
    populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("sql/catalog.sql")));
    populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("sql/data.sql")));

    populator.populate(dataSource.getConnection());
}

R2DBC

您可以使用 ConnectionFactoryInitializer 在加载 Spring 上下文时初始化所有添加到此初始化器的填充器。

@Bean
public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {

    var populator = new CompositeDatabasePopulator();
    populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("sql/schema.sql")));
    populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("sql/catalog.sql")));
    populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("sql/data.sql")));


    var initializer = new ConnectionFactoryInitializer();
    initializer.setConnectionFactory(connectionFactory);
    initializer.setDatabasePopulator(populator);

    return initializer;
}
,

您可以使用 @Sql 注释您的测试类。然后它应该在整个测试中只执行一次。然后也可以在方法级别进行个别调整。