【问题标题】:Testcontainer's Redis container connects to a different container then the one defined in the testTestcontainers Redis 容器连接到另一个容器,然后是测试中定义的容器
【发布时间】:2018-11-24 04:37:00
【问题描述】:

我正在我的 Spring Boot 应用程序中进行集成测试。该应用需要使用 Redis。

在开发阶段,我有一个应用程序连接到的 Redis 本地容器。

对于集成测试,我使用的是testcontainers,我也关注了他们的example of how to use a Redis container

在某些时候,我明白只有在开发容器启动并运行时才能正确运行测试。如果它关闭,则集成测试正在下降,因为它们无法到达 Redis。

所以集成测试类看起来像这样:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SharkApplication.class,
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(locations = "classpath:application-integrationtests.yml")
@AutoConfigureMockMvc
public class SharkIntegrationTest {
static GenericContainer redis = new GenericContainer("redis:3.0.6")
        .withExposedPorts(6379);

@BeforeClass
public static void before(){
    redis.start();
}

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

运行测试时,我可以在日志中看到:

14:36:24.372 [main] DEBUG ???? [redis:3.0.6] - Starting container: redis:3.0.6
14:36:24.372 [main] DEBUG ???? [redis:3.0.6] - Trying to start container: 
   redis:3.0.6
14:36:24.373 [main] DEBUG ???? [redis:3.0.6] - Trying to start container: 
    redis:3.0.6 (attempt 1/1)
14:36:24.373 [main] DEBUG ???? [redis:3.0.6] - Starting container: redis:3.0.6
14:36:24.373 [main] INFO ???? [redis:3.0.6] - Creating container for image: 
   redis:3.0.6
...
14:36:25.282 [main] INFO ???? [redis:3.0.6] - Container redis:3.0.6 started

但随后应用程序失败,因为它无法访问 Redis:

Caused by: redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refused: connect

在某些时候,我尝试更改容器应该启动的端口。从 6379 到 16379(在代码和 yml 文件中都更改了),但随后测试进入无限循环并打印到屏幕:

14:41:57.258 [ducttape-0] DEBUG org.testcontainers.containers.ExecInContainerPattern - /amazing_beaver: Running "exec" command: /bin/bash -c </dev/tcp/localhost/16379 && echo

【问题讨论】:

  • 您可以使用@DynamicPropertySource 动态(DynamicPropertyRegistry 注册表)添加主机/端口。

标签: java redis integration-testing testcontainers


【解决方案1】:

您错过了 Testcontainers 的一个非常重要的方面 - 随机端口。

从你提到的链接:

例如,对于上面的 Redis 示例,以下内容将允许您的测试访问 Redis 服务:
String redisUrl = redis.getContainerIpAddress() + ":" + redis.getMappedPort(6379);

Testcontainers 使用随机端口启动所有内容以避免冲突。

您可以关注this workshop进行正确整合。

【讨论】:

  • 好的,我明白你在说什么。您还知道如何在测试期间将 mappedPort 注入应用程序吗?我在这里的另一个线程中问了同样的问题:stackoverflow.com/questions/50894302/…
  • 是的,请检查我发布的链接。到目前为止,静态块 + 设置属性是最好的方法。我们(Testcontainers)将很快改变我们的 Spring Boot 示例以使用这种方法。
  • RedisClient 怎么样,它只是 Redis Server 对吧?所以在其他连接Redis Server时,我们需要创建一个Redis Client,connectionString为redis://redisContainer.getContainerIpAddress() + redisContainer.getMappedPort(REDIS_PORT)
【解决方案2】:

当您以这种方式声明容器时:

static GenericContainer redis = new GenericContainer("redis:3.0.6")
    .withExposedPorts(6379);

您告诉 TestContainers 将随机主机端口映射到容器端口 6379。如以下截图所示,例如TestContainers从主机端口32881映射到容器端口6379

在测试中访问Redis容器,需要使用随机主机端口,而不是redis端口6379。为此,您需要覆盖(在运行时)application.properties 中定义的配置值以使用随机主机端口。

你可以这样做:

package some.random.packagee;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.testcontainers.containers.GenericContainer;

@SpringBootTest
@ContextConfiguration(initializers = some.random.packagee.AbstractContainerBaseTest.Initializer.class)
public class AbstractContainerBaseTest {

    private static final int REDIS_PORT = 6379;

    // Optional
    @Autowired
    private RedisTemplate redisTemplate;

    // Optional 
    protected void cleanCache() {
        redisTemplate.getConnectionFactory().getConnection().flushAll();
    }

    public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

        static GenericContainer redis = new GenericContainer<>("redis:6-alpine")
            .withExposedPorts(REDIS_PORT)
            .withReuse(true);

        @Override
        public void initialize(ConfigurableApplicationContext context) {
            // Start container
            redis.start();

            // Override Redis configuration
            String redisContainerIP = "spring.redis.host=" + redis.getContainerIpAddress();
            String redisContainerPort = "spring.redis.port=" + redis.getMappedPort(REDIS_PORT); // <- This is how you get the random port.
            TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context,  redisContainerIP, redisContainerPort); // <- This is how you override the configuration in runtime.
        }
    }
}

然后在需要使用Redis的类中扩展类AbstractContainerBaseTest,例如:

package some.random.packagee;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

class CacheTest extends AbstractContainerBaseTest {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @AfterEach
    void tearDown() {
        cleanCache();
    }

    @Test
    public void testSomeMethodUsingRedis() {
        // Add your test here.
    }
}

【讨论】:

    猜你喜欢
    • 2019-03-17
    • 2015-09-13
    • 2019-12-22
    • 2018-01-26
    • 2018-10-27
    • 2017-10-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多