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-jupiter 和 testcontainers-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 中的新功能。