您的位置:首页 >SpringBoot整合Testcontainers实现数据库与中间件集成测试
发布于2026-07-30 阅读(0)
扫一扫,手机访问
单元测试可以 Mock 掉数据库,但集成测试需要真实环境。Testcontainers 在测试时启动 Docker 容器,用完后自动销毁。

说实话,单元测试里用 Mock 爽是爽,但一到集成测试,假数据就露馅了。真刀真枪地测数据库、测缓存,就得有真实环境。过去我们得自己搭测试数据库、手动清理数据,麻烦不说,还容易污染环境。Testcontainers 的出现,算是把这个问题从根上解决了——它直接在测试时拉起 Docker 容器,跑完自动销毁,干干净净,不留痕迹。
下面直接上干货,看看怎么在 Spring Boot 项目里把它用起来。
先用 Ma ven 把 Testcontainers 的核心包和 MySQL 模块加进来:
org.testcontainers testcontainers 1.19.3 test org.testcontainers mysql 1.19.3 test
版本号建议统一,这里用的是 1.19.3,实际使用时可以换成最新的稳定版。
最经典的场景——测试数据库操作。写一个用户仓库的测试类,用 @Testcontainers 注解标记,然后声明一个静态的 MySQL 容器:
@SpringBootTest
@Testcontainers
class UserRepositoryTest {
@Container
static MySQLContainer> mysql = new MySQLContainer<>("mysql:8.0")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", mysql::getJdbcUrl);
registry.add("spring.datasource.username", mysql::getUsername);
registry.add("spring.datasource.password", mysql::getPassword);
}
@Autowired
private UserRepository userRepository;
@Test
void testSa veAndFind() {
User user = new User();
user.setName("张三");
user.setEmail("zhangsan@test.com");
userRepository.sa ve(user);
assertThat(user.getId()).isNotNull();
User found = userRepository.findById(user.getId()).orElse(null);
assertThat(found).isNotNull();
assertThat(found.getName()).isEqualTo("张三");
}
}
关键点在于 @DynamicPropertySource——它把容器启动后的 JDBC 地址、用户名、密码动态注入到 Spring 的配置中,这样测试就能连上临时数据库了。测试方法里就是常规的增删查改,完全不需要关心数据库在哪里。
缓存测试同理,再加一个 Redis 的依赖:
org.testcontainers redis 1.19.3 test
然后写个测试类,用 GenericContainer 启动 Redis 7 镜像,暴露 6379 端口:
@SpringBootTest
@Testcontainers
class RedisCacheTest {
@Container
static GenericContainer> redis = new GenericContainer<>(
DockerImageName.parse("redis:7-alpine"))
.withExposedPorts(6379);
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.redis.host", redis::getHost);
registry.add("spring.redis.port", () -> redis.getMappedPort(6379));
}
@Autowired
private StringRedisTemplate redisTemplate;
@Test
void testSetAndGet() {
redisTemplate.opsForValue().set("test:key", "hello");
String value = redisTemplate.opsForValue().get("test:key");
assertThat(value).isEqualTo("hello");
}
}
注意这里用的是 GenericContainer,因为它没有专用的 Redis 模块(其实有,但用通用容器也能搞定)。同样动态注入 host 和 port,测试里直接操作 RedisTemplate,和真实环境没区别。
如果项目里既需要 MySQL 又需要 Redis,每个测试类都单独启动容器会很慢。一个常见的做法是抽一个抽象基类,把容器声明成静态的,让所有集成测试继承它:
@Testcontainers
abstract class AbstractIntegrationTest {
@Container
static MySQLContainer> mysql = new MySQLContainer<>("mysql:8.0");
@Container
static GenericContainer> redis = new GenericContainer<>(
DockerImageName.parse("redis:7-alpine"))
.withExposedPorts(6379);
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", mysql::getJdbcUrl);
registry.add("spring.datasource.username", mysql::getUsername);
registry.add("spring.datasource.password", mysql::getPassword);
registry.add("spring.redis.host", redis::getHost);
registry.add("spring.redis.port", () -> redis.getMappedPort(6379));
}
}
这样一来,所有子类测试都能复用同一个 MySQL 和 Redis 容器,容器在整个测试套件中只会启动一次,大大节省时间。而且容器用完后自动销毁,不会残留任何数据——这才是集成测试该有的样子。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8