如何 运行 为 spring 数据 elasticsearch 使用动态端口测试容器

how to run testcontainer with dynamic port for spring data elasticsearch

我的测试用例使用@SpringBootTest 注释来调出上下文并自动装配了一些存储库。 Testcontainer 在 @BeforeAll() 方法中启动。问题是 RestClientConfig 在测试用例中的 @BeforeAll() 之前是 initialized/injected。当 testcontainer 启动时,它会导出一些动态端口。

我必须在 testcontainer 34343 中设置一些固定端口,并在 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)))));

有没有办法启动容器并获取其动态端口,然后使用它来初始化 RestClientConfig?

虽然我没有使用注释@Testcontainers。有必要吗?

  1. 您可以使用上下文配置初始化程序在运行时设置属性,稍后可以在 RestClientConfig 中使用。

让我向您展示 Postgresql 容器设置的示例:

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

所有配置都在 DockerPostgreDataSourceInitializer 中完成,我在这里设置了我需要的所有属性。您还需要使用 @ContextConfiguration 注释对您的测试 class 进行注释。您可以对 ElasticSearchContainer 执行类似的操作。正如我刚刚检查的那样,ElasticSearchContainer 有一个方法 getHttpHostAddress() which returns host+dynamic_port combination for your container。您可以获得 host-port 对并在属性中设置,以便稍后在您的客户端配置中使用。如果您只需要端口,您可以调用 container.getMappedPort(9200) 并再次在属性中设置该端口。

  1. 关于@Testcontainers注解,如果你想让testcontainers管理你的容器生命周期,你需要它。在这种情况下,您还需要使用 @Container 注释来注释容器。如果您的容器是静态字段,则您的容器将在 class 中的所有测试方法之前启动一次,如果它是常规字段,则将在每个测试方法之前启动一次。您可以在此处阅读更多相关信息:https://www.testcontainers.org/test_framework_integration/junit_5/#extension。 或者您可以在 @BeforeAll 或 @BeforeEach 注释设置方法中手动启动容器。换句话说,不,您不必使用 @Testcontainers 注释。

较新版本的 Spring 正好针对此用例提供 @DynamicPropertySourcehttps://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/DynamicPropertySource.html

您的代码应大致如下所示:

 @SpringJUnitConfig(...)
 @Testcontainers
 class ExampleIntegrationTests {

     @Container
     static ElasticsearchContainer elastic= new ElasticsearchContainer(ELASTICSEARCH_IMAGE)
        .withEnv("discovery.type", "single-node");

     // ...

     @DynamicPropertySource
     static void elasticProperties(DynamicPropertyRegistry registry) {
         registry.add("spring.elasticsearch.rest.uris", elastic::getHttpHostAddress);
     }

 }