Контейнер Redis Testcontainer подключается к другому контейнеру, отличному от того, который определен в тесте

Я провожу интеграционные тесты в своем приложении Spring Boot. Приложению требуется Redis для работы.

На этапе разработки у меня есть локальный контейнер Redis, к которому подключается приложение.

Для интеграционных тестов я использую тестовые контейнеры, и я также следил за их пример использования контейнера Redis.

В какой-то момент я понял, что тест запускается правильно только тогда, когда контейнер разработки запущен и работает. Если он не работает, интеграционные тесты падают, потому что они не могут добраться до 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

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
0
4 166
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вам не хватает очень важного аспекта 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 запускает все со случайными портами, чтобы избежать конфликтов.

Вы можете следовать этот семинар, чтобы правильно интегрировать его.

Хорошо, я понимаю, о чем вы говорите. Вы также знаете, как ввести этот mappedPort в приложение во время тестирования? Я задавал те же вопросы в другой ветке здесь: stackoverflow.com/questions/50894302/…

riorio 17.06.2018 08:54

Да, пожалуйста, проверьте ссылку, которую я разместил. статический блок + установка свойств пока лучший способ сделать это. Мы (Testcontainers) скоро изменим наш пример Spring Boot, чтобы использовать этот подход.

bsideup 18.06.2018 09:59

Когда вы объявляете контейнер таким образом:

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

Затем вы расширяете класс AbstractContainerBaseTest на классы, которые требуют использования Redis, например:

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

Другие вопросы по теме