A common pattern when using Testcontainers is to use @DynamicPropertySource to configure the properties required to use the service in the Testcontainers-managed container. For example, you might do something like this to use Redis:
@Container
static GenericContainer redis = new GenericContainer(DockerImageName.parse("redis").withTag("4.0.14"));
// …
@DynamicPropertySource
static void redisProperties(DynamicPropertyRegistry registry) {
registry.add("spring.data.redis.host", redis::getHost);
registry.add("spring.data.redis.port", redis::getFirstMappedPort);
}
We can reduce this boilerplate by automatically extracting connection details from the service in the container and making them available to the auto-configuration:
@Container
@RedisServiceConnection
static GenericContainer redis = new GenericContainer(DockerImageName.parse("redis").withTag("4.0.14"));
Here, @RedisServiceConnection indicates that the container should be used a source of Redis connection details. Spring Boot will provide built-in support for extracting those details from the container while still allowing the Testcontainers API to be used to define and configure the container.
Comment From: rajadilipkolli
I have a use case where I am connecting to postgres using webflux but managing sql schema using liquibase, so in current process I need to set both database properties and Liquibase properties in @Dynamicpropertysource , will this update take care of this usecase as well?
Comment From: wilkinsona
Yes. A single container will be able to provide multiple connections. Here's an example of a test that uses R2DBC with the database initialized using Liquibase (Flyway is also supported):
@DataR2dbcTest
@Testcontainers(disabledWithoutDocker = true)
class CityRepositoryTests {
@Container
@JdbcServiceConnection
@R2dbcServiceConnection
static PostgreSQLContainer<?> postgresql = new PostgreSQLContainer<>(DockerImageNames.postgresql())
.withDatabaseName("test");
@Autowired
private CityRepository repository;
@Test
void databaseHasBeenInitialized() {
StepVerifier.create(this.repository.findByState("DC").filter((city) -> city.getName().equals("Washington")))
.consumeNextWith((city) -> assertThat(city.getId()).isNotNull())
.verifyComplete();
}
}
Comment From: joschi
Does this feature/how does this feature relate to https://github.com/PlaytikaOSS/testcontainers-spring-boot?
Comment From: wilkinsona
TIL about testcontainers-spring-boot… There's no relationship between the two.
Comment From: jucosorin
Hi @wilkinsona
First of all, the new @ServiceConnection and related annotations are a great addition for running integration tests with Testcontainers.
At the company I work for, I have created a Spring Boot Starter which is used for configuring Spring Kafka. The main autoconfiguration class kicks in with:
@AutoConfiguration(after = TaskSchedulingAutoConfiguration.class)
@EnableConfigurationProperties(StreamingKafkaProperties.class)
@ConditionalOnClass(name = "org.springframework.kafka.annotation.EnableKafka")
@ConditionalOnProperty(value = "mm.kafka.enabled", havingValue = "true")
@Import({
StreamingKafkaSASLAutoConfiguration.class,
StreamingKafkaProducerAutoConfiguration.class,
StreamingKafkaConsumerAutoConfiguration.class,
StreamingKafkaRetryAutoConfiguration.class,
KafkaMessageService.class,
AlertingMessageCreator.class,
AlertingProducerService.class
})
public class StreamingKafkaAutoConfiguration {
We are doing integration tests using Testcontainers, and before this feature was introduced we were using the following to get the spring.kafka.bootstrap-servers injected into the Spring tests:
@Container
private static final KafkaContainer kafkaContainer = new KafkaContainer(DockerImageName.parse(TESTCONTAINERS_KAFKA_IMAGE))
.withEnv("KAFKA_AUTO_CREATE_TOPICS_ENABLE", "FALSE");
@DynamicPropertySource
static void setProperties(DynamicPropertyRegistry registry) {
registry.add("spring.kafka.bootstrap-servers", kafkaContainer::getBootstrapServers);
}
I wanted to switch to using @ServiceConnection and the code changed to:
@Container
@KafkaServiceConnection
private static final KafkaContainer kafkaContainer = new
KafkaContainer(DockerImageName.parse(TESTCONTAINERS_KAFKA_IMAGE))
.withEnv("KAFKA_AUTO_CREATE_TOPICS_ENABLE", "FALSE");
The problem is that with this approach, the @KafkaServiceConnection test autoconfiguration kicks in after the code in my Spring Boot Starter and this results in "spring.kafka.bootstrap-servers" always being set to the default value of "PLAINTEXT://locahost:9092". The only solution to make this work is to add back the @DynamicPropertySource code since this injects the Testcontainers Kafka container port in my autoconfiguration classes.
Is there a more elegant solution to this? Am I not using this as it should be used? As I understand it, the whole point of using @KafkaServiceConnection is to not need to use the @DynamicPropertySource.
And btw, switching back to just using @DynamicPropertySource doesn't work either now because of https://github.com/spring-projects/spring-boot/issues/34770
Comment From: wilkinsona
@jucosorin Thanks for trying out the milestone. Can you please open a separate issue for this?
Comment From: sergey-morenets
Hi @wilkinsona
Are the code samples in this thread still relevant? I upgraded to Spring Boot 3.1.0 but couldn't find annotation @JdbcServiceConnection.
Comment From: eddumelendez
Hi, it's only @ServiceConnection now. See https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.testing.testcontainers.service-connections
Comment From: sergey-morenets
Hi @eddumelendez
Thank you for the quick response.
Comment From: pandamaroder
Hi everyone and @wilkinsona, could you please help-
what a package name for this @RedisServiceConnection to import from?
Comment From: pandamaroder
ive tried this
testImplementation("com.redis:testcontainers-redis:2.2.2")
implementation("org.springframework.boot:spring-boot-starter-data-redis")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.boot:spring-boot-testcontainers:2.6.0")
testImplementation("org.springframework.boot:spring-boot-test-autoconfigure:2.6.0")
Comment From: wilkinsona
As we worked on this, we removed the need for service-specific annotations so there's no @RedisServiceConnection. You should use @ServiceConnection instead. You can learn more in the documentation.
Comment From: rajadilipkolli
Hi @wilkinsona ,
For databases and others adding @ServiceConnection is enough but for using redis in SB 3.4.0-M1 , I still need to use @ServiceConnection("redis") . Is this done to support both redis and Redis-stack containers?
@Bean
@ServiceConnection("redis")
RedisContainer redisContainer() {
return new RedisContainer(DockerImageName.parse("redis").withTag("7.4.0-alpine"));
}
Comment From: scottfrederick
@rajadilipkolli There is an explanation of this in the documentation that Andy linked to above:
If you’re using a
@Beanmethod, Spring Boot won’t call the bean method to get the Docker image name, because this would cause eager initialization issues. Instead, the return type of the bean method is used to find out which connection detail should be used. This works as long as you’re using typed containers, e.g.Neo4jContainerorRabbitMQContainer. This stops working if you’re usingGenericContainer, e.g. with Redis, as shown in the following example:
What is the RedisContainer class that you are using? Is it an implementation of GenericContainer or something else?
Comment From: wilkinsona
Could be related to https://github.com/spring-projects/spring-boot/issues/41450.
Comment From: rajadilipkolli
yes, related to #41450, I am trying the milestone release in preparation for 3.4.0 release.