概述

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-jupitertestcontainers-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 上找到。