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

cvn*_*new 12 unit-testing h2 liquibase spring-data-jpa spring-boot

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

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

我一直在遵循 Spring 参考指南在启动时设置Liquibase 执行。我的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>
Run Code Online (Sandbox Code Playgroud)

我的测试文件如下所示:

 @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...
         // ...
     }
 }
Run Code Online (Sandbox Code Playgroud)

应用上下文类:

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

       public static void main(String... args) {
           SpringApplication.run(PersistenceTestConfig.class, args);
       }
  }
Run Code Online (Sandbox Code Playgroud)

根据参考指南,

默认情况下,Liquibase 在您的上下文中自动装配 (@Primary) DataSource 并将其用于迁移。如果您需要使用不同的数据源,您可以创建一个并将其@Bean 标记为@LiquibaseDataSource。如果您这样做并且想要两个数据源,请记住创建另一个并将其标记为@Primary。或者,您可以通过在外部属性中设置 spring.liquibase.[url,user,password] 来使用 Liquibase 的本机数据源。设置 spring.liquibase.url 或 spring.liquibase.user 足以使 Liquibase 使用自己的数据源。如果三个属性中的任何一个尚未设置,则将使用其等效 spring.datasource 属性的值。

显然,我希望我的测试使用与 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
Run Code Online (Sandbox Code Playgroud)

这不起作用,因为 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))]
Run Code Online (Sandbox Code Playgroud)

所以,我去掉了明确的 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
Run Code Online (Sandbox Code Playgroud)

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

如何使用 Liquibase 生成的测试进行测试?

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

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

任何帮助深表感谢!

Les*_*iak 8

问题在于@DataJpaTest你正在使用。见文档@DataJpaTest

默认情况下,使用@DataJpaTest 注释的测试将使用嵌入式内存数据库(替换任何显式或通常自动配置的数据源)。@AutoConfigureTestDatabase 注释可用于覆盖这些设置。

这意味着您的自动配置的数据源被覆盖,并且spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS corp不考虑url

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

EmbeddedDataSourceBeanFactoryPostProcessor : Replacing 'dataSource' DataSource bean with embedded version
Run Code Online (Sandbox Code Playgroud)

要修复,请使用:

spring.test.database.replace=none
Run Code Online (Sandbox Code Playgroud)


cvn*_*new 5

总结解决方案...根据 @Lesiak 的建议,我@AutoConfigureTestDatabase在测试类中添加了注释,以覆盖@DataJpaTest. (为我错过了 Javadoc 中明显的内容而感到羞耻!)测试类现在看起来像这样:

   @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 {
       ...
    }
Run Code Online (Sandbox Code Playgroud)

上下文配置:

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

     public static void main(String... args) {
          SpringApplication.run(PersistenceTestConfig.class, args);
     }
Run Code Online (Sandbox Code Playgroud)

}

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
Run Code Online (Sandbox Code Playgroud)

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

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

谢谢你,@Lesiak,你的帮助!