概述
Testcontainers 是一个用于单元测试目的的 Java 库,它允许我们在测试时创建临时的 Docker 容器。当需要避免使用实际服务器进行测试时,它非常有用。
在这个教程中,我们将学习如何在测试使用 Redis 的 Spring Boot 应用程序时利用 Testcontainers。
2. 项目设置
使用任何测试容器的前提是 在运行测试的机器上安装 Docker。
一旦安装了 Docker,我们就可以开始设置我们的 Spring Boot 应用了。
在这个应用中,我们将设置一个 Redis 哈希表、一个仓库和一个服务,该服务将使用仓库与 Redis 进行交互。
2.1. 依赖项
首先,我们需要在项目中添加所需的依赖。
首先,我们将添加 Spring Boot 开发者工具测试 和 Spring Boot 数据 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.19.7</version>
<scope>test</scope>
</dependency>
2.2. 自动配置
由于我们不需要任何 高级配置,我们可以使用自动配置来设置连接到 Redis 服务器。
为此,我们需要在 application.properties
文件中添加 Redis 连接细节:
spring.redis.host=127.0.0.1
spring.redis.port=6379
3. 应用程序设置
现在我们从代码开始构建主应用程序。我们将创建一个读写产品到 Redis 数据库的小应用。
3.1. 实体
首先,我们来看 Product
类:
@RedisHash("product")
public class Product implements Serializable {
private String id;
private String name;
private double price;
// Constructor, getters and setters
}
@RedisHash
注解告诉 Spring Data Redis 这个类应该存储在 Redis 哈希表中。对于不包含嵌套对象的实体,作为哈希存储是合适的。
3.2. 仓库
接下来,我们可以定义一个针对 Product
哈希的仓库:
@Repository
public interface ProductRepository extends CrudRepository<Product, String> {
}
CRUD 仓库接口已经实现了保存、更新、删除和查找产品的所需方法。所以我们不需要自己定义方法。
3.3. 服务
最后,让我们创建一个服务,使用 ProductRepository
执行读写操作:
@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);
}
// other methods
}
这个服务可以被控制器或服务用来执行产品上的 CRUD 操作。
在实际应用中,这些方法可能包含更复杂的逻辑,但在本教程中,我们只关注 Redis 交互。
4. 测试
现在我们将编写对 ProductService
的测试,以测试 CRUD 操作。
4.1. 测试服务
让我们为 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());
}
这假设有一个 Redis 数据库正在使用 properties 文件中指定的 URL 运行。如果我们的服务器无法连接到运行中的 Redis 实例,或者没有运行 Redis 实例,测试将会失败。
4.2. 使用 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. 改变连接细节
此时,我们有一个运行的 Redis 容器,但还没有更改应用程序使用的连接细节。要做到这一点,我们只需要在 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 容器的替代配置
另一种选择是使用 Jupiter 集成,通过 @Testcontainers
注解管理 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());
}
很好!我们看到 Redis 容器可供测试方法使用。此外,我们无需更改其他测试方法,可以直接使用它们。
5. 总结
在这篇文章中,我们学习了如何使用 Redis Testcontainer 运行测试。我们也了解了一些关于 Spring Data Redis 的内容,以便更好地使用它。
如往常一样,示例代码可以在 GitHub 上找到。