使用TestContainers轻松构建Spring Boot 3.1 + Redis测试环境

我想做的事情

使用Spring Boot(>= 3.1)来实现与Redis的读写功能。构建测试环境。测试运行时使用TestContainers功能访问Redis。

随着Spring Boot升级至v3版本,相应的变更点也产生了,并且需要进行适配。

环境

春季引导:3.1.0

TestContainers 是什么?

TestContainers是一个用于在Java的JUnit测试中使用Docker容器的库。
在Spring Boot测试中,它被用来测试依赖于实际数据库、队列等外部资源的部分,以便在生产环境中进行相同环境的测试。

例如,如果应用程序依赖于PostgreSQL数据库,那么如果本地环境没有安装PostgreSQL,将无法执行测试,这将导致问题。

然而,使用TestContainers,我们可以在测试运行时临时构建一个基于Docker容器的PostgreSQL环境,并在该环境中执行测试。

一旦测试结束,该容器将自动删除,因此可以将对环境造成的影响最小化。

由于TestContainers使用Docker,所以在执行测试时需要在测试机器上安装Docker。

設置依存関係

在Gradle环境中进行以下配置。如果您是使用Maven的话,请相应地进行更改。

dependencies {
    // SpringからRedisにアクセスするのに必要
    implementation("org.springframework.boot:spring-boot-starter-data-redis")

    // Testcontainers を利用するのに必要
    testImplementation("org.testcontainers:junit-jupiter")
}

启动Docker守护程序

使用TestContainers需要在执行测试的计算机上安装了Docker。如果您正在使用Docker Desktop,请启动Docker Desktop并运行Docker Daemon。

# Docker daemonが動いていることを確認
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS  PORTS  NAMES

注意:解决在Mac环境下无法访问Docker Daemon的问题。

在Mac环境中,当尝试使用TestContainers功能时,可能无法成功启动Docker容器。
如果遇到这种情况,请事先创建以下链接。

$ sudo ln -s $HOME/.docker/run/docker.sock /var/run/docker.sock

您好,TestContainers似乎正在尝试通过$HOME/.docker/run/docker.sock访问Docker守护进程,但在Mac环境中这里可能为空。因此,我们将使用上述的链接来确保可以进行访问。

情报来源:由于“找不到有效的Docker环境”,测试容器测试用例失败。

将TestContainers的配置写入测试代码中。

package com.example.app.repository

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.testcontainers.containers.GenericContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import org.testcontainers.utility.DockerImageName

@SpringBootTest
@Testcontainers
class SomeRepositoryTest {

    @Autowired
    lateinit var redisTemplate: RedisTemplate<String, String>

    companion object {
        @Container
        val redis = GenericContainer(DockerImageName.parse("redis:5.0.3-alpine"))
            .withExposedPorts(6379)

        @DynamicPropertySource
        @JvmStatic
        fun redisProperties(registry: DynamicPropertyRegistry) {
            registry.add("spring.data.redis.host", redis::getContainerIpAddress)
            registry.add("spring.data.redis.port") { redis.getMappedPort(6379) }
        }
    }
}

我们逐个进行查看。

设定所需的注释。

// Spring Boot を有効にして、Autowiredが使えるようにする
@SpringBootTest
// Testcontainers を有効にする
@Testcontainers

获取 RedisTemplate 实例

    // Autowired によって、RedisTemplateインスタンスを取得する
    @Autowired
    lateinit var redisTemplate: RedisTemplate<String, String>

这将在稍后用于对Redis进行读写操作。

进行 Testcontainers 的设置

    companion object {
        // 起動するRedisのDockerコンテナの情報を取得する
        // 利用するDockerイメージを `redis:5.0.3-alpine` とする
        @Container
        val redis = GenericContainer(DockerImageName.parse("redis:5.0.3-alpine"))
            // Port の設定をする
            .withExposedPorts(6379)

        // 動的にプロパティ情報を設定する
        @DynamicPropertySource
        @JvmStatic
        fun redisProperties(registry: DynamicPropertyRegistry) {
            // IP Address, Port をコンテナから取得した情報の通り、設定する
            registry.add("spring.data.redis.host", redis::getContainerIpAddress)
            registry.add("spring.data.redis.port") { redis.getMappedPort(6379) }
        }
    }

通过使用 @DynamicPropertySource,可以确保容器信息在每次启动时发生变化时能够对其进行适应。
如果在 application.properties 中进行设置,只能处理固定值,因此需要使用 @DynamicPropertySource。

请注意

            registry.add("spring.data.redis.host", redis::getContainerIpAddress)
            registry.add("spring.data.redis.port") { redis.getMappedPort(6379) }

在这里设置的是 spring.data.redis. ,但在 Spring Boot v3 中变为这样了。
如果使用旧版本,则是 spring.redis. 。
要注意的是,旧的文章可能保持旧的写法而无法正常工作,所以要小心。

消息來源:Redis 屬性

写测试

    @Test
    fun test0() {
        redisTemplate.opsForValue().set("hello", "WORLD!!!")

        val data = redisTemplate.opsForValue().get("hello")

        assertEquals("WORLD!!!", data)
    }

执行测试后,在Docker Desktop中可以看到Redis容器被创建并且在测试结束后被销毁(大约在1秒内销毁)。

简化TestContainers的配置

在Spring Boot 3.1及以上版本中,可以使用ServiceConnection功能。使用该功能可以简化TestContainers的配置。

要使用此功能,需要在依赖中添加以下内容。

dependencies {
    ...()...
    // Spring v3.1 で入った機能を使うのに必要。
    testImplementation("org.springframework.boot:spring-boot-testcontainers")
    ...()...
}

然后,按照以下方式在一个合适的文件中创建Configuration。

@TestConfiguration
class TestContainerConfig {
    @Bean
    @ServiceConnection(name = "redis")
    fun redisContainer(): GenericContainer<*> {
        return GenericContainer(DockerImageName.parse("redis:5.0.3-alpine"))
            .withExposedPorts(6379)
    }
}

在使用Redis时需要注意的是,

    @ServiceConnection(name = "redis")

需要将 name 设置为 “redis”。

image.png

从这个例子可以看出,在关系型数据库(RDB)和其他类型的数据库中,都有各自专用的容器,因此不需要使用通用容器(GenericContainer)。

通过这样做,可以将测试代码简化为以下方式。

@SpringBootTest
@Testcontainers
@Import(TestContainerConfig::class)
class SomeRepositoryTest {
    @Autowired
    lateinit var redisTemplate: RedisTemplate<String, String>

// 以下の内容を省略できるようになる
//    companion object {
//        @Container
//        val redis = GenericContainer(DockerImageName.parse("redis:5.0.3-alpine"))
//            .withExposedPorts(6379)
//
//        @DynamicPropertySource
//        @JvmStatic
//        fun redisProperties(registry: DynamicPropertyRegistry) {
//            registry.add("spring.data.redis.host", redis::getContainerIpAddress)
//            registry.add("spring.data.redis.port") { redis.getMappedPort(6379) }
//        }
//    }

    @Test
    fun test0() {
        redisTemplate.opsForValue().set("hello", "WORLD!!!")

        val data = redisTemplate.opsForValue().get("hello")

        assertEquals("WORLD!!!", data)
    }
}

简单解释一下的话,

@Import(TestContainerConfig::class)

为了导入刚刚创建的带有 @TestConfiguration 标注的 TestContainerConfig 类的设置,这是必要的。
对于 @Configuration,设置会自动导入,但对于 @TestConfiguration,需要明确使用 @Import 导入,请注意。
由于这样做可以降低意外导入不必要设置的风险,因此更推荐使用 @TestConfiguration。

即使采用上述设置,应该也会以相同方式运作。

总结

以上就是使用TestContainers临时启动Redis容器进行测试的方法。

广告
将在 10 秒后关闭
bannerAds