当前位置: 首页 > 知识库问答 >
问题:

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

金霄
2023-03-14

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

(注意:我们使用SpringBoot2.1.3.RELEASE和MySql作为主要数据库,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>

我的测试文件如下所示:

 @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...
         // ...
     }
 }

应用上下文类:

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

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

根据参考指南,

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

显然,我希望我的测试使用与Liquibase用于初始化数据库的相同的数据源实例。因此,首先,我试图指定spring.datasource属性,而不提供spring.liquibase.[url、user、密码]属性——假设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。数据源属性定义,仅提供以下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测试中,这些测试不需要任何其他东西。

应该很简单。。。然而,我还没有找到正确的方法,也找不到任何文档可以清楚地解释如何解决这个问题。

任何帮助都非常感谢!

共有2个答案

隗翰海
2023-03-14

总结一下解决方案...根据@Lesiak的建议,我已经在我的测试类中添加了@AutoConfigreTestDatabase注释,以覆盖由@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 {
       ...
    }

上下文配置:

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

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

}

我的应用程序。测试/资源中的属性

  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模式以及所有表等。如果没有指向use corp;脚本的@Sql注释,测试就会开始,但对于在表上使用corp.前缀的Spring-Data-JPA生成的查询,测试似乎是可以的。也就是说,当为映射到具有显式指定目录的表的实体类生成查询时:@Table(name="my_table",catalog="corp")。如果测试试图使用未显式映射到“corp”目录的实体,则会引发SQL异常,说明未找到该表——就好像它在其他默认模式中查找该表一样。因此,我已经向测试类添加了@Sql注释(如上所示),以在测试之前执行use corp;语句。完成了任务。(注意,添加spring.jpa.properties.hibernate.default_schema=corp到配置中似乎没有任何效果。)

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

翟俊
2023-03-14

问题在于您正在使用的@DataJpaTest。请参阅@DataJpaTest

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

这意味着自动配置的数据源被覆盖,urlspring。数据源。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
 类似资料:
  • 但是,我也有希望持久化的特定于应用程序的数据--我希望将其存储在一个文件中。 这可能吗?

  • 我们的使用和配置基于https://github.com/hdineth/springboot-freemaker-email-send: 但是,任何地方都没有关于如何使用JUnit5为此运行单元测试的信息或文档。 当我添加相关的依赖项时 在我们的实际代码中(这是一个大的多模块项目),我有另一个错误,原因是: 但这只是为了上下文,首先我想让它在简单的示例项目中工作,然后担心让它在复杂的项目中工作。

  • 我想测试我的SpringBoot应用程序,它使用cassandra作为CrudRepository。我最终得到了 具有 和 这就导致了 如果我使用旧版本的cassandra-unit-Spring 它以NullPointerException结束,因为没有注入值repo。 来源https://github.com/StephanPraetsch/spring.boot.cassandra.unit

  • 使用 GWT 更轻松地测试异步应用程序 您可能从编写 Ajax 应用程序中获得了极大乐趣,但是对它们执行单元测试却着实让人头痛。 在本文中,Andrew Glover 着手解决 Ajax 的弱点(其中之一),即应对异步 Web 应用程序执行单元测试的固有挑战。 幸运的是,他发现在 Google Web Toolkit 的帮助下,解决这个特殊的代码质量问题要比预想的容易。 Ajax 在近期无疑是 W

  • 我计划使用PostgreSQL作为我的Quarkus应用程序的数据库,但我希望在测试中使用H2方便。 有没有办法完成这样的壮举?

  • 我需要在整个Android应用程序中使用一个类的单个实例。我正在使用以下代码来实现这一点,我想确保这样做是正确的、线程安全的并且不会对性能产生影响。 每当我在应用程序中需要我的类的实例时,我就调用: 所以,我不想确定我在这里没有做错什么,这种方法在开发过程中不会造成任何问题,如果有更好的替代方案的话。