繁体   English   中英

如何在 spring 启动集成测试期间正确连接到测试容器 redis?

[英]How to connect to testcontainers redis correctly during spring boot integration test?

我正在为 spring 启动中的服务编写测试

@Component
public class MyService {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    // a number of other @Autowired dependencies

    public User getUser(String uuid) {
        var key = String.format("user:%s", uuid);
        var cache = stringRedisTemplate.opsForValue().get(key);
        if (cache == null) {
            // return user from database
        } else {
            // return user from deserialized cache
        }
    }
}

@Testcontainers
@SpringBootTest
class MyServiceTest {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    MyService myService;
    
    @Container
    public static GenericContainer<?> redis =
        new GenericContainer<>("redis:5.0.14-alpine3.15").withExposedPorts(6379);

    @BeforeClass
    public static void startContainer() {
        redis.start();
        var redisUrl = String.format("redis://%s:%s", redis.getHost(), redis.getMappedPort(6379));
        System.setProperty("spring.redis.url", redisUrl);
    }

    @AfterClass
    public static void stopContainer() {
        redis.stop();
    }

    @Test
    void getUser_returnCachedUser() {
        // breakpoint here
        stringRedisTemplate.opsForValue().set("user:some-uuid", "{\"uuid\":\"some-uuid\",\"name\":\"cache\"}");
        var user = myService.getUser("some-uuid");
        assertEquals("cache", user.name);
    }
}

当我在调试模式下运行它并打断点时,我注意到控制台中的端口redis.getMappedPort(6379)不等于stringRedisTemplate.connectionFactory.clientmyService.stringRedisTemplate.connectionFactory.client

System.setProperty是否覆盖了属性并在这种情况下生效? 如何在 spring 启动集成测试中使用测试容器?

我建议使用与构建在测试容器之上的playtika略有不同的容器。

您需要做的是在pom.xml中包含spring-cloud-starter-bootstrap (作为测试依赖项就足够了)。

然后在您的测试application.yaml|properties中使用以下内容:

spring:
  redis:
    port: ${embedded.redis.port}
    password: ${embedded.redis.password}
    host: ${embedded.redis.host}
    ssl: false

您可以使用getFirstMappedPort()而不是redis.getMappedPort(6379)因为 testcontainer 使用随机端口。 6379是宿主机端口,但是redis容器的端口是随机分配的,避免冲突。 更多细节可以在另一个线程中找到: https ://stackoverflow.com/a/50869731

我在 Java 和 Kotlin 中准备了 2 个模板,它们对我有用(servlet 不是 webflux),供您参考。

// AbstractIntegrationTest.java
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;

@SpringBootTest
@Testcontainers
@DirtiesContext
public abstract class AbstractIntegrationTest {
    public static final GenericContainer<?> redis =
        new GenericContainer<>(DockerImageName.parse("redis:5.0.14-alpine3.15"))
            .withExposedPorts(6379);

    public static final GenericContainer<?> mysql =
        new GenericContainer<>(DockerImageName.parse("mysql:5.7.28"))
            .withExposedPorts(3306)
            .withEnv("MYSQL_ROOT_PASSWORD", "password")
            .withEnv("MYSQL_USER", "test")
            .withEnv("MYSQL_PASSWORD", "password")
            .withEnv("MYSQL_DATABASE", "db_test");

    static {
        redis.start();
        mysql.start();
    }

    @DynamicPropertySource
    static void registerProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.redis.url", () -> String.format(
            "redis://%s:%s",
            redis.getHost(),
            redis.getMappedPort(6379)
        ));
        registry.add("spring.datasource.url", () -> String.format(
            "jdbc:mysql://%s:%s@%s:%s/%s",
            "root",
            "password",
            mysql.getHost(),
            mysql.getMappedPort(3306),
            "db_test"
        ));
    }
}

// MyServiceTest.java
@Testcontainers
@SpringBootTest
@DirtiesContext
class MyServiceTest extends AbstractIntegrationTest {
    @Autowired
    MyService myService;

    @Test
    void someMethod() {
        // preparation

        // invocation
        myService.someMethod()

        // assertion
    }
}

这是 Kotlin 版本:

// AbstractIntegrationTest.kt
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.annotation.DirtiesContext
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.testcontainers.containers.GenericContainer
import org.testcontainers.junit.jupiter.Testcontainers
import org.testcontainers.utility.DockerImageName

@SpringBootTest
@Testcontainers
@DirtiesContext
abstract class AbstractIntegrationTest {
    companion object {
        @JvmStatic
        val redis: GenericContainer<*> = GenericContainer(DockerImageName.parse("redis:5.0.14-alpine3.15"))
            .withExposedPorts(6379)

        @JvmStatic
        val mysql: GenericContainer<*> = GenericContainer(DockerImageName.parse("mysql:5.7.28"))
            .withExposedPorts(3306)
            .withEnv("MYSQL_ROOT_PASSWORD", "password")
            .withEnv("MYSQL_USER", "test")
            .withEnv("MYSQL_PASSWORD", "password")
            .withEnv("MYSQL_DATABASE", "db_test")

        init {
            redis.start()
            mysql.start()
        }

        @DynamicPropertySource
        @JvmStatic
        fun registerProperties(registry: DynamicPropertyRegistry) {
            registry.add("spring.redis.url") {
                String.format(
                    "redis://%s:%s",
                    redis.host,
                    redis.getMappedPort(6379)
                )
            }
            registry.add("spring.datasource.url") {
                String.format(
                    "jdbc:mysql://%s:%s@%s:%s/%s",
                    "root",
                    "password",
                    mysql.host,
                    mysql.getMappedPort(3306),
                    "db_test"
                )
            }
        }
    }
}

// MyServiceTest.kt
@SpringBootTest
internal class MyServiceTest : AbstractIntegrationTest() {
    @Autowired
    lateinit var myService: MyService

    @Test
    fun someMethod() {
        // preparation

        // invocation
        myService.someMethod()

        // assertion
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM