KafkaContainer - how to read kafka container port as property in Spring Boot after start() / how to configure Kafka port before starting

Viewed 1270

I am doing integration testing with KafkaContainer; after starting the container, the port is chosen randomly.

I can get the port by container.getBootStrapServer(), but what I need is to put the dynamic host+port in the properties (application-itest.yml, under spring.kafka.bootstrap-servers, because that is where Spring Kafka picks the value to configure my bean of KafkaTemplate).

Or, if I can set the port to be a static value while starting KafkaContainer, like 9999, and set it in the yaml file, it also will work. I don't know how to do neither.(Do I need to inject another bean of KafkaTemplate after getting the value?)

How do I start KafkaContainer:

    public static final KafkaContainer kafka = new KafkaContainer(
            DockerImageName.parse("confluentinc/cp-kafka:5.4.3")
    );
    private AdminClient adminClient;

    ...

    @BeforeEach
    void setup() {
        if (adminClient == null) {
            kafka.start();
            // here value is dynamic and is not accessible in Environment; not as `@AutoConfigureWiremock`, I have `wiremock.server` value set after)
            String bootStrapServer = kafka.getBootstrapServers();
            log.info("Kafka container created at: {}", bootStrapServer);
            adminClient = AdminClient.create(Map.of(
                    AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootStrapServer
            ));
            // create my topic
            log.info("Current topic: {}", topic);
            adminClient.createTopics(Set.of(
                    new NewTopic(topic, 4, (short) 1)
            ));
        }
    }

2 Answers

You can use @DynamicPropertySource:

Method-level annotation for integration tests that need to add properties with dynamic values to the Environment's set of PropertySources. This annotation and its supporting infrastructure were originally designed to allow properties from Testcontainers based tests to be exposed easily to Spring integration tests. However, this feature may also be used with any form of external resource whose lifecycle is maintained outside the test's ApplicationContext.

Methods annotated with @DynamicPropertySource must be static and must have a single DynamicPropertyRegistry argument which is used to add name-value pairs to the Environment's set of PropertySources. Values are dynamic and provided via a Supplier which is only invoked when the property is resolved.

In your test class:

import org.testcontainers.junit.jupiter.Container;

@Container
private static KafkaContainer kafkaContainer = new KafkaContainer(DockerImageName.parse(KAFKA_IMAGE));

@DynamicPropertySource
static void kafkaProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.kafka.bootstrap-servers", () -> { 
        return kafkaContainer.getHost() + ":" + kafkaContainer.getFirstMappedPort();
    });    
}

I find no way to do it. At last I turn to EmbeddedKafka and populate the host+port with SpEL "spring.kafka.bootstrap-servers=${spring.embedded.kafka.brokers}". That works even with @TestPropertySource(properties={xxx}).

Basically in this post: https://blog.mimacom.com/testing-apache-kafka-with-spring-boot-junit5/

Remember that the property setting should be done at test level, because when you only want to use Kafka in some itest, adding the property to application-itest.yml will cause all itest classes to look for EmbeddedKafka in the classpath and panic when you don't add @EmbeddedKafka on them.

Related