有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

JavaTestContainer的Redis容器与测试中定义的容器连接到不同的容器

我正在我的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


共 (2) 个答案

  1. # 1 楼答案

    您缺少Testcontainers的一个非常重要的方面—随机端口

    从您提到的链接:

    For example, with the Redis example above, the following will allow your tests to access the Redis service:
    String redisUrl = redis.getContainerIpAddress() + ":" + redis.getMappedPort(6379);

    Testcontainers使用随机端口启动一切,以避免冲突

    您可以按照this workshop正确地集成它

  2. # 2 楼答案

    以这种方式声明容器时:

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

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

    docker ps

    要在测试中访问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.
        }
    }