使用 Liquibase 为 Spring 启动应用程序中的单元测试初始化​​内存中的 H2

Using Liquibase to initialize in-memory H2 for unit tests in Spring Boot application

我在 Spring JPA 测试中多次使用内存数据库,从未遇到过问题。这一次,我有一个更复杂的模式要初始化,并且该模式必须有一个自定义名称(我们领域模型中的一些实体与特定的目录名称相关联)。因此,出于这个原因,以及确保为了确保测试完全同步并与我们初始化和维护模式的方式一致,我正在尝试在 Spring Data JPA[=60= 之前使用 Liquibase 初始化内存中的 H2 数据库] 执行存储库单元测试。

(注意:我们使用Spring Boot 2.1.3.RELEASEMySql作为我们的主数据库,H2 仅用于测试。)

我一直在按照 Spring 参考指南设置 Liquibase executions on startup。我的 Maven POM 中有以下条目:

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.liquibase</groupId>
        <artifactId>liquibase-core</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-test-autoconfigure</artifactId>
        <scope>test</scope>
    </dependency>

我的测试文件是这样的:

 @RunWith(SpringRunner.class)
 @ContextConfiguration(classes = PersistenceTestConfig.class)
 @DataJpaTest
 public class MyRepositoryTest {

     @Autowired
     private MyRepository myRepository;

     @Test
     public void someDataAccessTest() {
         // myRepository method invocation and asserts here...
         // ...
     }
 }

应用上下文 class:

  @EnableJpaRepositories({"com.mycompany.myproject"})
  @EntityScan({"com.mycompany.myproject"})
  public class PersistenceTestConfig {

       public static void main(String... args) {
           SpringApplication.run(PersistenceTestConfig.class, args);
       }
  }

根据参考指南,

By default, Liquibase autowires the (@Primary) DataSource in your context and uses that for migrations. If you need to use a different DataSource, you can create one and mark its @Bean as @LiquibaseDataSource. If you do so and you want two data sources, remember to create another one and mark it as @Primary. Alternatively, you can use Liquibase’s native DataSource by setting spring.liquibase.[url,user,password] in external properties. Setting either spring.liquibase.url or spring.liquibase.user is sufficient to cause Liquibase to use its own DataSource. If any of the three properties has not be set, the value of its equivalent spring.datasource property will be used.

显然,我希望我的测试使用与 Liquibase 用于初始化数据库的数据源实例相同的数据源实例。因此,起初,我尝试指定 spring.datasource 属性而不提供 spring.liquibase.[url, user, password] 属性——假设 Liquibase 然后将使用默认的主要 Spring 数据源:

spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS corp
spring.datasource.username=sa
spring.datasource.password=

spring.jpa.hibernate.ddl-auto=validate

# LIQUIBASE (LiquibaseProperties)
spring.liquibase.change-log=classpath:db.changelog.xml
#spring.liquibase.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS corp
#spring.liquibase.user=sa
#spring.liquibase.password=
spring.liquibase.default-schema=CORP
spring.liquibase.drop-first=true

这没有用,因为 Liquibase 没有找到我 必须 创建表的 CORP 模式:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'liquibase' defined in class path resource  [org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguratio n$LiquibaseConfiguration.class]: Invocation of init method failed; nested  exception is liquibase.exception.DatabaseException:  liquibase.command.CommandExecutionException: liquibase.exception.DatabaseException: liquibase.exception.LockException: liquibase.exception.DatabaseException: Schema "CORP" not found; SQL statement:
 CREATE TABLE CORP.DATABASECHANGELOGLOCK (ID INT NOT NULL, LOCKED BOOLEAN NOT NULL, LOCKGRANTED TIMESTAMP, LOCKEDBY VARCHAR(255), CONSTRAINT PK_DATABASECHANGELOGLOCK PRIMARY KEY (ID)) [90079-197] [Failed SQL: CREATE TABLE CORP.DATABASECHANGELOGLOCK (ID INT NOT NULL, LOCKED BOOLEAN NOT NULL, LOCKGRANTED TIMESTAMP, LOCKEDBY VARCHAR(255), CONSTRAINT PK_DATABASECHANGELOGLOCK PRIMARY KEY (ID))]

所以,我去掉了明确的 spring.datasource 属性 定义,只提供了以下 Liquibase 属性:

 spring.jpa.hibernate.ddl-auto=validate

 # LIQUIBASE (LiquibaseProperties)
 spring.liquibase.change-log=classpath:db.changelog.xml
 spring.liquibase.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS corp
 spring.liquibase.user=sa
 spring.liquibase.password=
 spring.liquibase.default-schema=CORP
 spring.liquibase.drop-first=true

这导致 Liquibase 任务成功执行,并且似乎在启动时将所有必要的表和数据加载到其 本机 数据源中 - 使用提供的变更日志文件。我知道发生这种情况是因为我已经明确设置了 Liquibase DS 属性,并且根据 Spring 文档,这将导致 Liquibase 使用其自己的本机数据源。我想,出于这个原因,虽然 Liquibase 作业现在成功运行,但测试仍在尝试使用不同的 [Spring 默认值?] 数据源,并且数据库模式未通过预测试验证。 (未找到 "corp" 模式,没有表。)因此,很明显,测试使用的数据源实例与我尝试使用 Liquibase 生成的数据源实例不同。

如何让测试使用 Liquibase 生成的内容?

我尝试的任何方法似乎都不起作用。我怀疑我正在使用的自动配置和显式配置之间存在某种冲突。在这种情况下 @DataJpaTest 是一个好方法吗?我确实想将我的应用程序上下文配置限制为严格的 JPA 测试,这些测试不需要任何其他东西。

应该很简单...但是我一直找不到正确的方法,也找不到任何文档可以清楚地解释如何解决这个问题。

非常感谢任何帮助!

问题出在@DataJpaTest您正在使用。 见 Documentation of @DataJpaTest

By default, tests annotated with @DataJpaTest will use an embedded in-memory database (replacing any explicit or usually auto-configured DataSource). The @AutoConfigureTestDatabase annotation can be used to override these settings.

这意味着您的 auto-configured 数据源被覆盖,并且 url spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS corp 未被考虑在内

你会在日志中找到类似的东西

EmbeddedDataSourceBeanFactoryPostProcessor : Replacing 'dataSource' DataSource bean with embedded version

要修复,请使用:

spring.test.database.replace=none

总结解决方案...根据@Lesiak 的建议,我已将 @AutoConfigureTestDatabase 注释添加到我的测试 class 以覆盖 @DataJpaTest 强加的默认数据源的使用。 (我很遗憾错过了 Javadoc 中明显的内容!)测试 class 现在看起来像这样:

   @RunWith(SpringRunner.class)
   @ContextConfiguration(classes = PersistenceTestConfig.class)
   @DataJpaTest
   @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
   @Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = {"classpath:init.sql"})
   public class MyRepoTest {
       ...
    }

上下文配置:

 @EnableJpaRepositories({"com.mycompany.myproject"})
 @EntityScan({"com.mycompany.myproject"})
 public class PersistenceTestConfig {

     public static void main(String... args) {
          SpringApplication.run(PersistenceTestConfig.class, args);
     }

}

我的 application.propertiestest/resources:

  spring.jpa.hibernate.ddl-auto=none

  # adding this line seems to make no difference (perhaps, it targets the default DS, not the one used by Liquibase and tests), but using @Sql to execute 'use corp;' statement before tests works!
  # spring.jpa.properties.hibernate.default_schema=corp

  # LIQUIBASE (LiquibaseProperties)
  spring.liquibase.change-log=classpath:db.changelog.xml
  spring.liquibase.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS corp
  spring.liquibase.user=sa
  spring.liquibase.password=
  spring.liquibase.default-schema=CORP
  #spring.liquibase.liquibase-tablespace=CORP
  spring.liquibase.drop-first=true

init.sql 脚本驻留在 /test/resources 中并包含一行:use corp;。 (这很重要,因为 我的一些 JPA 实体被显式映射到 corp 目录,而有些则没有,但在测试中它们都必须在同一个 corp 模式中找到.)

Liquibase 任务成功,我在日志中看到生成了 CORP 模式 - 包含所有 tables 等。没有 @Sql 注释指向 use corp; 脚本,测试开始,但似乎只有在 table 上使用 corp. 前缀的 Spring-Data-JPA-generated 查询才正常。也就是说,当为实体 classes 生成查询时,这些实体 classes 映射到具有显式指定目录的 tables:@Table(name="my_table", catalog="corp")。如果测试尝试使用未显式映射到 "corp" 目录的实体,则会抛出 SQL 异常,指出未找到 table - 就好像它正在寻找table 在其他一些默认架构中。因此,我在测试 class 中添加了 @Sql 注释(如上所示)以在测试之前执行 use corp; 语句。那完成了工作。 (注意在配置中加入spring.jpa.properties.hibernate.default_schema=corp貌似没有任何作用)

谢谢@Lesiak 的帮助!

您的架构名称必须与您的数据库名称相同。

你的情况下的正确答案如下所示

spring.liquibase.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS testdb

spring.liquibase.default-schema=testdb