Search code examples
spring-data-elasticsearchtestcontainers

how to run testcontainer with dynamic port for spring data elasticsearch


My test case uses @SpringBootTest annotations to bring up the context and has Autowired some repository. Testcontainer is started in @BeforeAll() method. The problem is RestClientConfig is being initialized/injected before @BeforeAll() in test case. When testcontainer starts, it exports some dynamic port.

I have to set some fixed port in testcontainer 34343 and use the same port in properties file for RestClientConfig.

container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE)
        .withEnv("discovery.type", "single-node")
        .withExposedPorts(9200)     
        .withCreateContainerCmdModifier(cmd -> cmd.withHostConfig(
                    new HostConfig().withPortBindings(new PortBinding(Ports.Binding.bindPort(34343), new ExposedPort(9200)))));

Is there a way to start container and get its dynamic port then use it to initialize RestClientConfig?

I didn't use annoation @Testcontainers though. Is it needed?


Solution

    1. You can use context configuration initialiser to set properties during runtime, which you can later use in your RestClientConfig.

    Let me show you on the example of Postgresql container setup:

    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class)
    @ContextConfiguration(initializers = AbstractTestcontainersTest.DockerPostgreDataSourceInitializer.class)
    public abstract class AbstractTestcontainersTest {
    
        protected static final String DB_CONTAINER_NAME = "postgres-auth-test";
        protected static PostgreSQLContainer<?> postgreDBContainer =
                new PostgreSQLContainer<>(DockerImageName.parse("public.ecr.aws/docker/library/postgres:12.10-alpine")
                        .asCompatibleSubstituteFor("postgres"))
                .withUsername("postgres")
                .withPassword("change_me")
                .withInitScript("db.sql")
                .withCreateContainerCmdModifier(cmd -> cmd.withName(DB_CONTAINER_NAME))
                .withDatabaseName("zpot_main");
    
        @BeforeAll
        public static void beforeAll() throws ShellExecutionException {
            postgreDBContainer.start();
        }
    
        @AfterAll
        public static void afterAll() {
            postgreDBContainer.stop();
        }
    
        public static class DockerPostgreDataSourceInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    
            @Override
            public void initialize(ConfigurableApplicationContext applicationContext) {
    
                TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
                        applicationContext,
                        "spring.datasource.url=" + postgreDBContainer.getJdbcUrl(),
                        "spring.datasource.username=" + postgreDBContainer.getUsername(),
                        "spring.datasource.password=" + postgreDBContainer.getPassword()
                );
            }
        }
    }
    

    All the configuration is done in DockerPostgreDataSourceInitializer, where I set all the properties I need. You also need to annotate your test class with @ContextConfiguration annotaion. You can do something similar with your ElasticSearchContainer. As I just checked the ElasticSearchContainer has a method getHttpHostAddress() which returns host+dynamic_port combination for your container. You can get that host-port pair and set in in properties to be used later in your client configuration. If you need just port you can call container.getMappedPort(9200) and again set that port in properties.

    1. Regarding @Testcontainers annotation, you need it if you want testcontainers to manage your container lifecycle. In that case you also need to annotate container with @Container annotation. Your container will be started either once before all test methods in a class if your container is a static field or before each test method if it's a regular field. You can read more about that here: https://www.testcontainers.org/test_framework_integration/junit_5/#extension. Or you can start your container manually either in @BeforeAll or @BeforeEach annotated setup methods. In other words no, you don't have to use @Testcontainers annotation.