1、概览 {#1概览}
Testcontainers 是一个用于创建临时 Docker 容器进行单元测试的 Java 库。当我们想要避免使用实际服务器进行测试时,它非常有用。
本文将会带你了解如何在 Spring Boot 中使用 Testcontainers 测试 Redis。
2、项目设置 {#2项目设置}
使用任何测试容器的首要前提是在运行测试的机器上安装 Docker。
安装好 Docker 后,就可以开始设置 Spring Boot 应用了。
在此应用中,我们将设置一个 Redis Hash、一个 Repository 和一个使用 Repository 与 Redis 交互的 Service。
2.1、依赖 {#21依赖}
添加所需的 spring-boot-starter-test 和 spring-boot-starter-data-redis 依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
接着,还要添加 Testcontainers 依赖:
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.17.2</version>
<scope>test</scope>
</dependency>
2.2、配置 {#22配置}
在 application.properties
文件中添加 Redis 连接的详细信息:
spring.redis.host=127.0.0.1
spring.redis.port=6379
3、应用设置 {#3应用设置}
我们要创建一个小型应用,向 Redis 数据库读写 Product(产品)。
3.1、Entity {#31entity}
创建 Product
类。
@RedisHash("product")
public class Product implements Serializable {
private String id;
private String name;
private double price;
// 构造函数、get、set 方法省略
}
@RedisHash
注解用于告诉 Spring Data Redis 该类应存储在 Redis Hash 中。
以 Hash 形式保存适用于不包含嵌套对象的实体。
3.2、Repository {#32repository}
接下来,为 Product
Hash 定义一个 Repository:
@Repository
public interface ProductRepository extends CrudRepository<Product, String> {
}
CrudRepository
接口已经实现了 save、update、delete 和 findXxx 方法。
3.3、Service {#33service}
最后,创建一个使用 ProductRepository
执行读写操作的 Service:
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public Product getProduct(String id) {
return productRepository.findById(id).orElse(null);
}
// 其他方法
}
然后,Controller 或其他 Service 就可以使用该 Service 对 Product 执行 CRUD 操作。
在实际应用中,这些方法可能包含更复杂的逻辑,但在本教程中,只关注 Redis 交互。
4、测试 {#4测试}
现在,为 ProductService
编写测试,以测试 CRUD 操作。
4.1、测试 Service {#41测试-service}
为 ProductService
写一个集成测试:
@Test
void givenProductCreated_whenGettingProductById_thenProductExistsAndHasSameProperties() {
Product product = new Product("1", "Test Product", 10.0);
productService.createProduct(product);
Product productFromDb = productService.getProduct("1");
assertEquals("1", productFromDb.getId());
assertEquals("Test Product", productFromDb.getName());
assertEquals(10.0, productFromDb.getPrice());
}
测试假设在 properties 中指定的 URL 上运行了一个 Redis 数据库。如果没有运行 Redis 实例或服务器无法连接,测试就会出错。
4.2、使用 Testcontainers 运行一个 Redis 容器 {#42使用-testcontainers-运行一个-redis-容器}
我们可以在运行时启动一个 Redis 测试容器来进行测试,这需要改动一些代码。
创建和运行测试容器的代码:
static {
GenericContainer<?> redis =
new GenericContainer<>(DockerImageName.parse("redis:5.0.3-alpine")).withExposedPorts(6379);
redis.start();
}
如上:
- 从
redis:5.0.3-alpine
镜像中创建了一个新容器。 - 默认情况下,Redis 实例在 6379 端口上运行。要暴露这个端口,可以使用
withExposedPorts()
方法。它会暴露该端口,并将其映射到主机上的一个随机端口。 start()
方法将启动容器并等待其准备就绪。- 将该代码添加到静态代码块中,以便在注入依赖和运行测试之前运行该代码。
4.3、更改连接信息 {#43更改连接信息}
此时,我们已经运行了 Redis 容器,但还没有更改应用使用的连接信息。
只需使用系统属性(System Properties)覆盖 application.properties
文件中的连接详细信息即可:
static {
GenericContainer<?> redis =
new GenericContainer<>(DockerImageName.parse("redis:5.0.3-alpine")).withExposedPorts(6379);
redis.start();
System.setProperty("spring.redis.host", redis.getHost());
System.setProperty("spring.redis.port", redis.getMappedPort(6379).toString());
}
将 spring.redis.host
属性设置为容器的 IP 地址。
获取 6379
的映射端口,然后设置 spring.redis.port
属性。
现在,当测试运行时,将连接到容器上运行的 Redis 数据库。
4.4、Redis 容器的其他配置 {#44redis-容器的其他配置}
另外,还可以通过 @Testcontainers
注解使用 Jupiter
集成来管理 Redis 容器的生命周期。使用该集成时,可以使用 @Container
注解来标记容器,以便进行生命周期管理。
接下来,我们使用这种方法为测试配置 Redis 容器。
首先,在项目的 pom.xml
文件中添加 junit-jupiter 和 testcontainers-redis-junit-jupiter 依赖项:
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.17.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.redis.testcontainers</groupId>
<artifactId>testcontainers-redis-junit-jupiter</artifactId>
<version>1.4.6</version>
<scope>test</scope>
</dependency>
接下来,使用 @Container
注解来定义 REDIS_CONTAINER
静态字段:
@Container
private static final RedisContainer REDIS_CONTAINER =
new RedisContainer(DockerImageName.parse("redis:5.0.3-alpine")).withExposedPorts(6379);
注意,定义为静态字段的容器将在测试方法之间共享,并且只会启动一次。
此外,使用 @DynamicPropertySource
注解配置 registerRedisProperties()
方法,为应用配置连接属性:
@DynamicPropertySource
private static void registerRedisProperties(DynamicPropertyRegistry registry) {
registry.add("spring.redis.host", REDIS_CONTAINER::getHost);
registry.add("spring.redis.port", () -> REDIS_CONTAINER.getMappedPort(6379).toString());
}
最后,验证配置是否按预期运行:
@Test
void givenRedisContainerConfiguredWithDynamicProperties_whenCheckingRunningStatus_thenStatusIsRunning() {
assertTrue(REDIS_CONTAINER.isRunning());
}
一切OK!我们可以看到,Redis 容器可用于测试方法。而且,无需更改其他测试方法,可以跟以前一样使用它们。
参考:https://www.baeldung.com/spring-boot-redis-testcontainers