使用SpringBoot和Testcontainers进行数据库集成测试| Baeldung

栏目: Java · 发布时间: 5年前

内容简介:Spring Data JPA提供了一种创建数据库查询并使用嵌入式H2数据库进行测试的简便方法。但在某些情况下,对真实数据库进行测试会更有利可图,特别是如果我们使用依赖于提供程序的查询。在本教程中,我们将演示如何使用

Spring Data JPA提供了一种创建数据库查询并使用嵌入式H2数据库进行测试的简便方法。

但在某些情况下,对真实数据库进行测试会更有利可图,特别是如果我们使用依赖于提供程序的查询。

在本教程中,我们将演示如何使用 Testcontainers 与Spring Data JPA和PostgreSQL数据库进行集成测试。

在我们之前的教程中,我们 主要使用@Query注释 创建了一些数据库 查询  ,我们现在将对其进行测试。

要在我们的测试中使用PostgreSQL数据库,我们必须添加 Testcontainers依赖 与测试范围和   PostgreSQL驱动 我们的pom.xml:

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.10.6</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.2.5</version>
</dependency>

我们还在test resources目录下创建一个application.properties文件,在该目录中我们指示Spring使用正确的驱动程序类,并在每次测试运行时创建和删除该方案:

spring.datasource.driver-<b>class</b>-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=create-drop

. 单一测试用法

要在单个测试类中开始使用PostgreSQL实例,我们必须首先创建容器定义,然后使用其参数建立连接:

@RunWith(SpringRunner.<b>class</b>)
@SpringBootTest
@ContextConfiguration(initializers = {UserRepositoryTCIntegrationTest.Initializer.<b>class</b>})
<b>public</b> <b>class</b> UserRepositoryTCIntegrationTest <b>extends</b> UserRepositoryCommonIntegrationTests {
 
    @ClassRule
    <b>public</b> <b>static</b> PostgreSQLContainer postgreSQLContainer = <b>new</b> PostgreSQLContainer(<font>"postgres:11.1"</font><font>)
      .withDatabaseName(</font><font>"integration-tests-db"</font><font>)
      .withUsername(</font><font>"sa"</font><font>)
      .withPassword(</font><font>"sa"</font><font>);
 
    <b>static</b> <b>class</b> Initializer
      implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        <b>public</b> <b>void</b> initialize(ConfigurableApplicationContext configurableApplicationContext) {
            TestPropertyValues.of(
              </font><font>"spring.datasource.url="</font><font> + postgreSQLContainer.getJdbcUrl(),
              </font><font>"spring.datasource.username="</font><font> + postgreSQLContainer.getUsername(),
              </font><font>"spring.datasource.password="</font><font> + postgreSQLContainer.getPassword()
            ).applyTo(configurableApplicationContext.getEnvironment());
        }
    }
}
</font>

在上面的示例中,我们使用   JUnit中的@ClassRule 在执行测试方法之前设置数据库容器。我们还创建了一个实现ApplicationContextInitializer的静态内部类  。 作为最后一步,我们将@ContextConfiguration批注应用于我们的测试类,初始化类作为参数。

通过执行这三个操作,我们可以在发布Spring上下文之前设置连接属性。

被测试的用例:

@Modifying
@Query(<font>"update User u set u.status = :status where u.name = :name"</font><font>)
<b>int</b> updateUserSetStatusForName(@Param(</font><font>"status"</font><font>) Integer status, 
  @Param(</font><font>"name"</font><font>) String name);
 
@Modifying
@Query(value = </font><font>"UPDATE Users u SET u.status = ? WHERE u.name = ?"</font><font>, 
  nativeQuery = <b>true</b>)
<b>int</b> updateUserSetStatusForNameNative(Integer status, String name);
</font>

使用配置的环境测试它们:

@Test
@Transactional
<b>public</b> <b>void</b> givenUsersInDB_WhenUpdateStatusForNameModifyingQueryAnnotationJPQL_ThenModifyMatchingUsers(){
    insertUsers();
    <b>int</b> updatedUsersSize = userRepository.updateUserSetStatusForName(0, <font>"SAMPLE"</font><font>);
    assertThat(updatedUsersSize).isEqualTo(2);
}
 
@Test
@Transactional
<b>public</b> <b>void</b> givenUsersInDB_WhenUpdateStatusForNameModifyingQueryAnnotationNative_ThenModifyMatchingUsers(){
    insertUsers();
    <b>int</b> updatedUsersSize = userRepository.updateUserSetStatusForNameNative(0, </font><font>"SAMPLE"</font><font>);
    assertThat(updatedUsersSize).isEqualTo(2);
}
 
<b>private</b> <b>void</b> insertUsers() {
    userRepository.save(<b>new</b> User(</font><font>"SAMPLE"</font><font>, </font><font>"email@example.com"</font><font>, 1));
    userRepository.save(<b>new</b> User(</font><font>"SAMPLE1"</font><font>, </font><font>"email2@example.com"</font><font>, 1));
    userRepository.save(<b>new</b> User(</font><font>"SAMPLE"</font><font>, </font><font>"email3@example.com"</font><font>, 1));
    userRepository.save(<b>new</b> User(</font><font>"SAMPLE3"</font><font>, </font><font>"email4@example.com"</font><font>, 1));
    userRepository.flush();
}
</font>

在上面的场景中,第一个测试以成功结束,但第二个测试抛出  InvalidDataAccessResourceUsageException  并显示以下消息:

Caused by: org.postgresql.util.PSQLException: ERROR: column <font>"u"</font><font> of relation </font><font>"users"</font><font> does not exist
</font>

如果我们使用H2嵌入式数据库运行相同的测试,则两个测试都将成功完成,但PostgreSQL不接受SET子句中的别名。我们可以通过删除有问题的别名来快速修复查询:

@Modifying
@Query(value = <font>"UPDATE Users u SET status = ? WHERE u.name = ?"</font><font>, 
  nativeQuery = <b>true</b>)
<b>int</b> updateUserSetStatusForNameNative(Integer status, String name);
</font>

这次两次测试都成功完成。在此示例中,我们使用Testcontainers来识别本机查询的问题,否则在切换到生产中的真实数据库之后会显示该问题。我们还应该注意到,使用JPQL查询通常更安全,因为Spring会根据所使用的数据库提供程序正确地进行转换。

共享数据库实例

在上一段中,我们描述了如何在单个测试中使用Testcontainers。在实际情况中,由于启动时间相对较长,我们希望在多个测试中重用相同的数据库容器。

现在让我们通过扩展PostgreSQLContainer  并覆盖  start()和stop()方法来创建数据库容器创建的公共类:

<b>public</b> <b>class</b> BaeldungPostgresqlContainer <b>extends</b> PostgreSQLContainer<BaeldungPostgresqlContainer> {
    <b>private</b> <b>static</b> <b>final</b> String IMAGE_VERSION = <font>"postgres:11.1"</font><font>;
    <b>private</b> <b>static</b> BaeldungPostgresqlContainer container;
 
    <b>private</b> BaeldungPostgresqlContainer() {
        <b>super</b>(IMAGE_VERSION);
    }
 
    <b>public</b> <b>static</b> BaeldungPostgresqlContainer getInstance() {
        <b>if</b> (container == <b>null</b>) {
            container = <b>new</b> BaeldungPostgresqlContainer();
        }
        <b>return</b> container;
    }
 
    @Override
    <b>public</b> <b>void</b> start() {
        <b>super</b>.start();
        System.setProperty(</font><font>"DB_URL"</font><font>, container.getJdbcUrl());
        System.setProperty(</font><font>"DB_USERNAME"</font><font>, container.getUsername());
        System.setProperty(</font><font>"DB_PASSWORD"</font><font>, container.getPassword());
    }
 
    @Override
    <b>public</b> <b>void</b> stop() {
        </font><font><i>//do nothing, JVM handles shut down</i></font><font>
    }
}
</font>

通过将  stop()方法留空,我们允许JVM处理容器关闭。我们还实现了一个简单的单例模式,其中只有第一个测试触发容器启动,每个后续测试使用现有实例。在  start()方法中,我们使用  System#setProperty 将连接参数设置为环境变量。

我们现在可以将它们放在application.properties 文件中:

spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}

现在让我们在测试定义中使用我们的实用程序类:

@RunWith(SpringRunner.<b>class</b>)
@SpringBootTest
<b>public</b> <b>class</b> UserRepositoryTCAutoIntegrationTest {
 
    @ClassRule
    <b>public</b> <b>static</b> PostgreSQLContainer postgreSQLContainer = BaeldungPostgresqlContainer.getInstance();
 
    <font><i>// tests</i></font><font>
}
</font>

与前面的示例一样,我们将@ClassRule 注释应用于包含容器定义的字段。这样,在创建Spring上下文之前,将使用正确的值填充DataSource连接属性。

现在,我们只需定义一个使用BaeldungPostgresqlContainer  实用程序类实例化的@ClassRule注释字段,  就可以使用相同的数据库实例实现多个测试。

结论

在本文中,我们介绍了使用Testcontainers对真实数据库实例执行测试的方法。

我们使用Spring 的ApplicationContextInitializer机制查看单个测试用法的示例  ,以及实现可重用数据库实例化的类。

我们还展示了Testcontainers如何帮助识别多个数据库提供程序的兼容性问题,尤其是对于本机查询。

与往常一样,本文中使用的完整代码可 在GitHub上获得


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

奔跑吧,程序员

奔跑吧,程序员

[美]叶夫根尼·布里克曼(Yevgeniy Brikman) / 吴晓嘉 / 人民邮电出版社 / 2018-7 / 99.00元

本书以软件工程师出身的创业者的角度,全面介绍了创业公司该如何打造产品、实现技术和建立团队,既是为创业者打造的一份实用入门指南,又适合所有程序员系统认识IT行业。书中内容分为三部分——技术、产品和团队,详细描绘创业的原始景象,具体内容包括:创业点子、产品设计、数据与营销、技术栈的选择、整洁的代码、软件交付、创业文化、招兵买马,等等。一起来看看 《奔跑吧,程序员》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试