1. 概述
本文将介绍 Spring Boot 3.1 中新增的 Testcontainers 增强支持功能。这次更新提供了更简洁的容器配置方式,并支持在本地开发时启动容器。使用 Testcontainers 进行开发和测试将变得更加无缝高效。
2. SpringBoot 3.1 之前的 Testcontainers 用法
Testcontainers 能在测试阶段创建类生产环境,从而避免使用 Mock,编写出与实现细节解耦的高质量自动化测试。
本文示例使用一个简单的 Web 应用,包含 MongoDB 持久化层和 REST 接口:
@RestController
@RequestMapping("characters")
public class MiddleEarthCharactersController {
private final MiddleEarthCharactersRepository repository;
// 构造方法省略
@GetMapping
public List<MiddleEarthCharacter> findByRace(@RequestParam String race) {
return repository.findAllByRace(race);
}
@PostMapping
public MiddleEarthCharacter save(@RequestBody MiddleEarthCharacter character) {
return repository.save(character);
}
}
集成测试中,我们会启动包含数据库的 Docker 容器。由于容器暴露的端口是动态分配的,无法在配置文件中硬编码数据库 URL。因此,在 3.1 之前的 Spring Boot 版本中,需要使用 @DynamicPropertySource
注解将动态属性注册到 DynamicPropertyRegistry
:
@Testcontainers
@SpringBootTest(webEnvironment = DEFINED_PORT)
class DynamicPropertiesIntegrationTest {
@Container
static MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:4.0.10"));
@DynamicPropertySource
static void setProperties(DynamicPropertyRegistry registry) {
registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl);
}
// ...
}
集成测试使用 @SpringBootTest
启动应用,并用 Testcontainers 搭建环境。最后使用 REST-assured 执行 HTTP 请求并验证响应:
@Test
void whenRequestingHobbits_thenReturnFrodoAndSam() {
repository.saveAll(List.of(
new MiddleEarthCharacter("Frodo", "hobbit"),
new MiddleEarthCharacter("Samwise", "hobbit"),
new MiddleEarthCharacter("Aragon", "human"),
new MiddleEarthCharacter("Gandalf", "wizzard")
));
when().get("/characters?race=hobbit")
.then().statusCode(200)
.and().body("name", hasItems("Frodo", "Samwise"));
}
3. 使用 @ServiceConnection
简化动态属性配置
从 SpringBoot 3.1 开始,可以使用 @ServiceConnection
注解消除动态属性配置的样板代码。
首先在 pom.xml
添加 spring-boot-testcontainers 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
然后移除注册动态属性的静态方法,直接在容器上添加 @ServiceConnection
注解:
@Testcontainers
@SpringBootTest(webEnvironment = DEFINED_PORT)
class ServiceConnectionIntegrationTest {
@Container
@ServiceConnection
static MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:4.0.10"));
// ...
}
@ServiceConnection
让 Spring Boot 自动配置动态注册所需属性。其内部通过容器类或 Docker 镜像名自动确定需要配置的属性。
支持该注解的容器和镜像列表见 Spring Boot 官方文档。
4. 本地开发中的 Testcontainers 支持
另一个激动人心的功能是 Testcontainers 能无缝集成到本地开发环境,只需少量配置。 这使得我们不仅在测试阶段,在本地开发时也能复现生产环境。
启用该功能需先创建 @TestConfiguration
,将所有 Testcontainers 声明为 Spring Bean,并添加 @ServiceConnection
注解:
@TestConfiguration(proxyBeanMethods = false)
class LocalDevTestcontainersConfig {
@Bean
@ServiceConnection
public MongoDBContainer mongoDBContainer() {
return new MongoDBContainer(DockerImageName.parse("mongo:4.0.10"));
}
}
由于 Testcontainers 依赖通常是 test
作用域,需从 test
包启动应用。在 test
包中创建调用主 main()
方法的启动类:
public class LocalDevApplication {
public static void main(String[] args) {
SpringApplication.from(Application::main)
.with(LocalDevTestcontainersConfig.class)
.run(args);
}
}
现在通过该 main()
方法启动应用,它会自动使用 MongoDB 数据库。用 Postman 发送 POST 请求后,可直接连接数据库验证数据持久化:
连接数据库需先获取容器暴露的端口,可通过应用日志或 docker ps
命令查看:
最后用 MongoDB 客户端通过 URL mongodb://localhost:63437/test
连接,查询 characters
集合:
成功连接并查询到本地开发环境 Testcontainers 启动的数据库数据。
5. 集成 DevTools 和 @RestartScope
本地开发时频繁重启应用会导致容器反复重启,但通过 Testcontainers 与 spring-boot-devtools
的集成,可以在应用重载时保持容器存活。 这个实验性功能能提升开发效率,节省时间并保留测试数据。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
然后修改本地开发配置,在 Testcontainers Bean 上添加 @RestartScope
注解:
@Bean
@RestartScope
@ServiceConnection
public MongoDBContainer mongoDBContainer() {
return new MongoDBContainer(DockerImageName.parse("mongo:4.0.10"));
}
现在从 test
包的 main()
方法启动应用,即可利用 spring-boot-devtools
的热重载功能。例如用 Postman 保存数据后重新编译应用:
修改请求路径(如从 "characters"
改为 "api/characters"
)并重新编译:
从应用日志或 Docker 可见数据库容器未重启。进一步验证应用重启后仍连接同一数据库:向新路径发送 GET 请求,确认之前插入的数据存在:
也可使用 Testcontainers API 的 withReuse(true)
方法:
@Bean
@ServiceConnection
public MongoDBContainer mongoDBContainer() {
return new MongoDBContainer(DockerImageName.parse("mongo:4.0.10"))
.withReuse(true);
}
这是更强大的方案,使容器生命周期独立于应用。启用复用后,应用重载或完全重启时容器仍保持运行。
6. 总结
本文介绍了 SpringBoot 3.1 的 Testcontainers 新特性:
- ✅ 使用
@ServiceConnection
替代@DynamicPropertySource
,简化配置 - ✅ 通过在
test
包创建启动类,将 Testcontainers 用于本地开发 - ✅ 结合
spring-boot-devtools
和@RestartScope
实现容器复用,提升开发效率
完整代码示例见 GitHub 仓库。