跳过正文

在 Spring Boot 集成测试中使用 Testcontainers

Spring Testcontainers Testing
作者
Harpal Singh
Software Engineer
翻译者
Namastecode
目录

1. 简介

在本教程中,我们将了解在创建集成测试时如何在Spring Boot应用程序中使用testcontainers。

Testcontainers 是轻量级的、可丢弃的基础设施服务实例,例如数据库、消息代理等,它们可以运行在 Docker 容器中。在编写集成测试时,我们希望能够使用这些服务,而不是模拟它们或者使用容易出错的自定义脚本来处理设置和拆卸。

让我们看看如何使用 testcontainers 设置一个数据库容器。

2. 前提条件

首先,我们需要设置项目以使用testcontainers。我们可以从官方网站下载并安装Docker: https://www.docker.com/get-started

接下来,我们需要在我们的 pom.xml 文件中添加 testcontainers 依赖项:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-testcontainers</artifactId>
</dependency>
<dependency
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>1.19.7</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.19.7</version>
    <scope>test</scope>
</dependency>

确保获取最新版本的 testcontainers junit-jupitertestcontainers-postgresql 依赖。同时,务必使用正确版本的 spring-boot-testcontainers,因为某些类在旧版本的 Spring Boot 中可能不可用。

3. 在Spring Boot测试中使用Testcontainers

Spring Boot 已经支持 Testcontainers 一段时间了。然而,随着 Spring Boot 3.1 的发布,这种集成得到了进一步的改进。让我们来看看旧方法与新方法的对比。

在 Spring Boot 3.1 之前的版本中,我们需要手动配置 Spring Boot 以连接到容器内运行的服务。这通常导致包含一些样板代码,涉及使用 @DynamicPropertySource 注解来设置必要的属性:

@SpringBootTest
@Testcontainers
class MyIntegrationTests {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");

    @Test
    void myTest() {
        // ...
    }

    @DynamicPropertySource
    static void postgresProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }
}

在上面,我们使用 @Testcontainer 注解对测试进行了标注,以告知测试类它使用了 testcontainers。然后,我们使用 @Container 注解定义了一个 PostgreSQL 容器的实例。这将启动一个指定版本的 PostgreSQL 容器。通常情况下,我们会在 application.properties 文件中配置连接属性。但由于我们使用的是 testcontainers,我们尚不知道连接属性是什么。因此,我们使用 @DynamicPropertySource 从容器实例中检索连接属性,并将其设置在 Spring Boot 应用程序上下文中。

正如我们所看到的,这种方法涉及一些移动部件,即使是一个简单的测试也显得非常冗长。

从 Spring Boot 3.1 开始,我们可以使用 @ServiceConnection 注解来简化这个过程:

@SpringBootTest
@Testcontainers
class MyIntegrationTests {

    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");

    @Test
    void myTest() {
        // ...
    }
}

在这里,我们用 @ServiceConnection 注解替换了 @DynamicPropertySource 注解。这消除了记住任何给定服务的所有属性的需要,并让 Spring Boot 自动处理连接配置。具体来说,它通过发现容器的类型并创建相应的 ConnectionDetails bean 来工作。然后,Spring Boot 使用它来配置与在 Testcontainer 中运行的服务的连接。

我们可以在官方的 Spring Boot 文档 中查看所有支持的容器。

4. 在开发过程中使用 Testcontainers

让我们更进一步,看看如何在开发过程中使用 Spring Boot 3.1 中的新功能来使用 Testcontainers。

从历史上看,我们过去常常使用不同的配置文件在开发和测试环境之间切换。但现在,有一种更好的方法来管理这一点,使开发环境尽可能与生产环境相似。

我们可以通过使用 testcontainers 创建一个新的 main 方法来在本地运行我们的应用程序,而不是创建一个测试配置文件:

public class TestMyApplication {
    public static void main(String[] args) {
        SpringApplication.from(MyApplication::main).run(args);
    }
}

我们必须在测试根包内创建这个类,以便将 testcontainers 的依赖项仅保持在测试范围内。否则,这些依赖项将被包含在整个 jar 文件中,这会增加应用程序的大小,并且在生产环境中不会使用。

现在,我们需要为PostgreSQL配置测试容器,并让新的main方法了解它。首先,我们创建一个配置文件,并使用*@TestConfiguration*注解:

@TestConfiguration(proxyBeanMethods = false)
class MyContainersConfiguration {
    @Bean
    @ServiceConnection
    PostgreSQLContainer<?> postgresContainer() {
        return new PostgreSQLContainer<>("postgres:16");
    }
}

简而言之,我们只是创建了一个代表PostgreSQL容器实例的bean。同样,我们使用 @ServiceConnection 注解它,以便Spring Boot知道应该管理其生命周期,并将其配置为与我们的应用程序一起使用。

现在,我们更新 test-main 方法:

public class TestMyApplication {
    public static void main(String[] args) {
        SpringApplication.from(MyApplication::main)
            .with(MyContainersConfiguration.class)
            .run(args);
    }
}

最后,我们可以运行 test-main 方法,通过 Maven 启动应用程序:

./mvnw spring-boot:test-run

或者使用 Gradle:

./gradlew bootTestRun

这将启动应用程序,并且容器将随着应用程序自动启动和关闭。正如我们所看到的,Maven和Gradle插件已经知道test-main方法,并将使用该方法启动应用程序。

5. 在重启之间保存容器数据

通过上述配置,我们在每次启动应用程序时都会创建一个新的容器实例。因此,我们会丢失所有数据。然而,我们可能希望在重启之间保存容器的状态。为此,我们可以在容器 bean 方法上使用来自 Spring Boot DevTools 的 @RestartScope 注解:

@Bean
@ServiceConnection
@RestartScope
PostgreSQLContainer<?> postgresContainer() {
    return new PostgreSQLContainer<>("postgres:16");
}

或者,我们可以通过在容器上调用 withReuse(true) 来使用 Testcontainers 中的实验性可重用容器功能:

new PostgreSQLContainer<>("postgres:16").withReuse(true);

这将会在重启之间保存数据。此外,这也加快了应用程序的启动时间,因为我们不会每次都创建一个新的容器。

6. 结论

在本文中,我们已经了解了如何在 Spring Boot 集成测试中使用 Testcontainers。我们已经了解了如何使用 Testcontainers 设置一个数据库容器,以及如何使用 @ServiceConnection 注解来简化这个过程。我们还了解了如何在开发过程中使用 Testcontainers,利用 Spring Boot 3.1 中的新功能。

相关文章

在Spring应用程序中返回HTTP 4XX错误
Spring HTTP
在 Spring 中获取 Properties 文件中定义的值
Spring Properties Basics
配置 Spring Boot 应用程序的端口
Spring Basics