Search code examples
spring-bootspring-boot-testtestcontainersspring-boot-testcontainers

How to use @ServiceConnection with GenericContainer and custom ConnectionDetails


I am using new @ServiceConnection annotation to manage KafkaContainer in my tests and it works as expected - KafkaContainerConnectionDetails is used during auto configuration to override bootstrap servers property.

Now, i also want to use custom GenericContainer subclass i created - SchemaRegistryContainer.

I would also like to manage this container with @ServiceConnection annotation, but it crashes with ConnectionDetailsNotFoundException: No ConnectionDetails found for source '@ServiceConnection source for Bean 'schemaRegistryContainer'.

I would like have my own custom ConnectionDetails (or some alternative way) for this @ServiceConnection, so that i can modify application properties i need when SchemaRegistryContainer starts. In this specific case, i need to modify property spring.kafka.consumer.properties.schema-registry-url with url from container.

Is this there a way to achieve this with @ServiceDefinition? I dont want to fall back to using static fields and @DynamicPropertySource.

My test configuration

@TestConfiguration(proxyBeanMethods = false)
public class ContainerConfig {
    public static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("confluentinc/cp-kafka");
    public static final String CONFLUENT_PLATFORM_VERSION = "7.4.1";

    @Bean
    @ServiceConnection
    KafkaContainer kafkaContainer() {
        return new KafkaContainer(DEFAULT_IMAGE_NAME.withTag(CONFLUENT_PLATFORM_VERSION));
    }

    @Bean
    @ServiceConnection
    SchemaRegistryContainer schemaRegistryContainer(KafkaContainer kafkaContainer) {
        return new SchemaRegistryContainer(CONFLUENT_PLATFORM_VERSION, kafkaContainer);
    }
}

SchemaRegistryContainer:

public class SchemaRegistryContainer extends GenericContainer<SchemaRegistryContainer> {

    private static final int PORT = 8081;

    public SchemaRegistryContainer(String confluentVersion, KafkaContainer kafka) {
        super("confluentinc/cp-schema-registry:" + confluentVersion);

        withExposedPorts(PORT);
        dependsOn(kafka);
        withEnv("SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS", kafka.getBootstrapServers());
    }

    public String getSchemaRegistryUrl() {
        return "http://" + getHost() + ":" + getMappedPort(PORT);
    }
}

Solution

  • I found out i was misunderstanding what @ServiceConnection was doing. In fact, this annotation is not needed at all, if i dont have custom ConnectionDetails but i still can use container as @Bean.

    For customizing properties after contaner starts, i used DynamicPropertyRegistry. This is in fact also mentioned in docs: https://docs.spring.io/spring-boot/docs/3.1.0/reference/htmlsingle/#features.testing.testcontainers.at-development-time.dynamic-properties

    New code:

        @Bean
        SchemaRegistryContainer schemaRegistryContainer(KafkaContainer kafkaContainer, DynamicPropertyRegistry dynamicPropertyRegistry) {
            SchemaRegistryContainer schemaRegistryContainer = new SchemaRegistryContainer(CONFLUENT_PLATFORM_VERSION, kafkaContainer);
            dynamicPropertyRegistry.add("spring.kafka.consumer.properties.schema.registry.url", schemaRegistryContainer::getTarget);
            dynamicPropertyRegistry.add("spring.kafka.producer.properties.schema.registry.url", schemaRegistryContainer::getTarget);
            return schemaRegistryContainer;
        }