Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testcontainer's Redis container connects to a different container then the one defined in the test

I'm doing integration tests in my Spring Boot app. The app needs a Redis to work with.

During development phase, I have a local container of Redis that the app connects to.

For the integration tests, I'm using testcontainers and I also followed their example of how to use a Redis container.

At some point I understood that the test run correctly only when the development container was up and running. If it is down, the integration tests are falling because they can't reach Redis.

So the integration test class looks like that:

@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();
}
...

When running the test, I can see this in the log:

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

But then the app fails as it can't reach Redis:

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

At some point I tried to change the port on which the container should start. From 6379 to 16379 (changed both in the code and in the yml file), but then the test is entering an endless loop and prints to the screen:

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

like image 238
riorio Avatar asked Jan 03 '23 06:01

riorio


2 Answers

You're missing a very important aspect of Testcontainers - random ports.

From the link you mentioned:

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 starts everything with random ports to avoid conflicts.

You can follow this workshop to integrate it properly.

like image 73
bsideup Avatar answered Jan 14 '23 13:01

bsideup


When you declare a container this way:

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

You are telling TestContainers to map a random host port to the container port 6379. As shown in the following screenshot, for example, TestContainers mapped from host port 32881 to container port 6379:

docker ps

To access the Redis container in a test, you need to use the random host port, not the redis port 6379. To do so, you need to override (in runtime) the configuration values defined in application.properties to use the random host port.

Here is how you can do it:

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.
        }
    }
}

Then you extend the class AbstractContainerBaseTest in the classes that require to use Redis, for example:

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.
    }
}
like image 34
Julian Avatar answered Jan 14 '23 13:01

Julian