Skip to content

Commit 3c095b4

Browse files
committed
Prefer DynamicPropertyRegistar to DynamicPropertyRegistry
Closes gh-41996
1 parent fdc4a51 commit 3c095b4

File tree

18 files changed

+340
-76
lines changed

18 files changed

+340
-76
lines changed

spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -364,9 +364,9 @@ TIP: You can use the Maven goal `spring-boot:test-run` or the Gradle task `bootT
364364
[[features.dev-services.testcontainers.at-development-time.dynamic-properties]]
365365
==== Contributing Dynamic Properties at Development Time
366366

367-
If you want to contribute dynamic properties at development time from your `Container` `@Bean` methods, you can do so by injecting a `DynamicPropertyRegistry`.
368-
This works in a similar way to the xref:testing/testcontainers.adoc#testing.testcontainers.dynamic-properties[`@DynamicPropertySource` annotation] that you can use in your tests.
369-
It allows you to add properties that will become available once your container has started.
367+
If you want to contribute dynamic properties at development time from your `Container` `@Bean` methods, define an additional `DynamicPropertyRegistrar` bean.
368+
The registrar should be defined using a `@Bean` method that injects the container from which the properties will be sourced as a parameter.
369+
This arrangement ensures that container has been started before the properties are used.
370370

371371
A typical configuration would look like this:
372372

spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/devservices/testcontainers/atdevelopmenttime/dynamicproperties/MyContainersConfiguration.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,22 @@
2020

2121
import org.springframework.boot.test.context.TestConfiguration;
2222
import org.springframework.context.annotation.Bean;
23-
import org.springframework.test.context.DynamicPropertyRegistry;
23+
import org.springframework.test.context.DynamicPropertyRegistrar;
2424

2525
@TestConfiguration(proxyBeanMethods = false)
2626
public class MyContainersConfiguration {
2727

2828
@Bean
29-
public MongoDBContainer mongoDbContainer(DynamicPropertyRegistry properties) {
30-
MongoDBContainer container = new MongoDBContainer("mongo:5.0");
31-
properties.add("spring.data.mongodb.host", container::getHost);
32-
properties.add("spring.data.mongodb.port", container::getFirstMappedPort);
33-
return container;
29+
public MongoDBContainer mongoDbContainer() {
30+
return new MongoDBContainer("mongo:5.0");
31+
}
32+
33+
@Bean
34+
public DynamicPropertyRegistrar mongoDbProperties(MongoDBContainer container) {
35+
return (properties) -> {
36+
properties.add("spring.data.mongodb.host", container::getHost);
37+
properties.add("spring.data.mongodb.port", container::getFirstMappedPort);
38+
};
3439
}
3540

3641
}

spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testcontainers/atdevelopmenttime/dynamicproperties/MyContainersConfiguration.kt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,18 +18,23 @@ package org.springframework.boot.docs.features.testcontainers.atdevelopmenttime.
1818

1919
import org.springframework.boot.test.context.TestConfiguration
2020
import org.springframework.context.annotation.Bean
21-
import org.springframework.test.context.DynamicPropertyRegistry
21+
import org.springframework.test.context.DynamicPropertyRegistrar;
2222
import org.testcontainers.containers.MongoDBContainer
2323

2424
@TestConfiguration(proxyBeanMethods = false)
2525
class MyContainersConfiguration {
2626

2727
@Bean
28-
fun mongoDbContainer(properties: DynamicPropertyRegistry): MongoDBContainer {
29-
var container = MongoDBContainer("mongo:5.0")
30-
properties.add("spring.data.mongodb.host", container::getHost)
31-
properties.add("spring.data.mongodb.port", container::getFirstMappedPort)
32-
return container
28+
fun mongoDbContainer(): MongoDBContainer {
29+
return MongoDBContainer("mongo:5.0")
30+
}
31+
32+
@Bean
33+
fun mongoDbProperties(container: MongoDBContainer): DynamicPropertyRegistrar {
34+
return DynamicPropertyRegistrar { properties ->
35+
properties.add("spring.data.mongodb.host") { container.host }
36+
properties.add("spring.data.mongodb.port") { container.firstMappedPort }
37+
}
3338
}
3439

3540
}

spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/ImportTestcontainersTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ void importWhenHasNonStaticContainerFieldThrowsException() {
103103
void importWhenHasContainerDefinitionsWithDynamicPropertySource() {
104104
this.applicationContext = new AnnotationConfigApplicationContext(
105105
ContainerDefinitionsWithDynamicPropertySource.class);
106-
assertThat(this.applicationContext.getEnvironment().containsProperty("container.port")).isTrue();
106+
assertThat(this.applicationContext.getEnvironment().getProperty("container.port")).isNotNull();
107107
}
108108

109109
@Test

spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationTests.java

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,22 @@
2121

2222
import com.redis.testcontainers.RedisContainer;
2323
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.api.extension.ExtendWith;
2425

2526
import org.springframework.boot.autoconfigure.AutoConfigurations;
2627
import org.springframework.boot.context.properties.ConfigurationProperties;
2728
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2829
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
29-
import org.springframework.boot.testcontainers.lifecycle.BeforeTestcontainerUsedEvent;
3030
import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleApplicationContextInitializer;
3131
import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable;
3232
import org.springframework.boot.testsupport.container.TestImage;
33+
import org.springframework.boot.testsupport.system.CapturedOutput;
34+
import org.springframework.boot.testsupport.system.OutputCaptureExtension;
3335
import org.springframework.context.ApplicationEvent;
3436
import org.springframework.context.annotation.Bean;
3537
import org.springframework.context.annotation.Configuration;
3638
import org.springframework.context.annotation.Import;
39+
import org.springframework.test.context.DynamicPropertyRegistrar;
3740
import org.springframework.test.context.DynamicPropertyRegistry;
3841

3942
import static org.assertj.core.api.Assertions.assertThat;
@@ -42,27 +45,78 @@
4245
* Tests for {@link TestcontainersPropertySourceAutoConfiguration}.
4346
*
4447
* @author Phillip Webb
48+
* @author Andy Wilkinson
4549
*/
4650
@DisabledIfDockerUnavailable
51+
@ExtendWith(OutputCaptureExtension.class)
4752
class TestcontainersPropertySourceAutoConfigurationTests {
4853

4954
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
5055
.withInitializer(new TestcontainersLifecycleApplicationContextInitializer())
5156
.withConfiguration(AutoConfigurations.of(TestcontainersPropertySourceAutoConfiguration.class));
5257

5358
@Test
54-
void containerBeanMethodContributesProperties() {
55-
List<ApplicationEvent> events = new ArrayList<>();
59+
@SuppressWarnings("removal")
60+
@Deprecated(since = "3.4.0", forRemoval = true)
61+
void registeringADynamicPropertyFailsByDefault() {
5662
this.contextRunner.withUserConfiguration(ContainerAndPropertiesConfiguration.class)
63+
.run((context) -> assertThat(context).getFailure()
64+
.rootCause()
65+
.isInstanceOf(
66+
org.springframework.boot.testcontainers.properties.TestcontainersPropertySource.DynamicPropertyRegistryInjectionException.class)
67+
.hasMessageStartingWith(
68+
"Support for injecting a DynamicPropertyRegistry into @Bean methods is deprecated"));
69+
}
70+
71+
@Test
72+
@SuppressWarnings("removal")
73+
@Deprecated(since = "3.4.0", forRemoval = true)
74+
void registeringADynamicPropertyCanLogAWarningAndContributeProperty(CapturedOutput output) {
75+
List<ApplicationEvent> events = new ArrayList<>();
76+
this.contextRunner.withPropertyValues("spring.testcontainers.dynamic-property-registry-injection=warn")
77+
.withUserConfiguration(ContainerAndPropertiesConfiguration.class)
78+
.withInitializer((context) -> context.addApplicationListener(events::add))
79+
.run((context) -> {
80+
TestBean testBean = context.getBean(TestBean.class);
81+
RedisContainer redisContainer = context.getBean(RedisContainer.class);
82+
assertThat(testBean.getUsingPort()).isEqualTo(redisContainer.getFirstMappedPort());
83+
assertThat(events.stream()
84+
.filter(org.springframework.boot.testcontainers.lifecycle.BeforeTestcontainerUsedEvent.class::isInstance))
85+
.hasSize(1);
86+
assertThat(output)
87+
.contains("Support for injecting a DynamicPropertyRegistry into @Bean methods is deprecated");
88+
});
89+
}
90+
91+
@Test
92+
@SuppressWarnings("removal")
93+
@Deprecated(since = "3.4.0", forRemoval = true)
94+
void registeringADynamicPropertyCanBePermittedAndContributeProperty(CapturedOutput output) {
95+
List<ApplicationEvent> events = new ArrayList<>();
96+
this.contextRunner.withPropertyValues("spring.testcontainers.dynamic-property-registry-injection=allow")
97+
.withUserConfiguration(ContainerAndPropertiesConfiguration.class)
5798
.withInitializer((context) -> context.addApplicationListener(events::add))
5899
.run((context) -> {
59100
TestBean testBean = context.getBean(TestBean.class);
60101
RedisContainer redisContainer = context.getBean(RedisContainer.class);
61102
assertThat(testBean.getUsingPort()).isEqualTo(redisContainer.getFirstMappedPort());
62-
assertThat(events.stream().filter(BeforeTestcontainerUsedEvent.class::isInstance)).hasSize(1);
103+
assertThat(events.stream()
104+
.filter(org.springframework.boot.testcontainers.lifecycle.BeforeTestcontainerUsedEvent.class::isInstance))
105+
.hasSize(1);
106+
assertThat(output)
107+
.doesNotContain("Support for injecting a DynamicPropertyRegistry into @Bean methods is deprecated");
63108
});
64109
}
65110

111+
@Test
112+
void dynamicPropertyRegistrarBeanContributesProperties(CapturedOutput output) {
113+
this.contextRunner.withUserConfiguration(ContainerAndPropertyRegistrarConfiguration.class).run((context) -> {
114+
TestBean testBean = context.getBean(TestBean.class);
115+
RedisContainer redisContainer = context.getBean(RedisContainer.class);
116+
assertThat(testBean.getUsingPort()).isEqualTo(redisContainer.getFirstMappedPort());
117+
});
118+
}
119+
66120
@Configuration(proxyBeanMethods = false)
67121
@EnableConfigurationProperties(ContainerProperties.class)
68122
@Import(TestBean.class)
@@ -77,6 +131,23 @@ RedisContainer redisContainer(DynamicPropertyRegistry properties) {
77131

78132
}
79133

134+
@Configuration(proxyBeanMethods = false)
135+
@EnableConfigurationProperties(ContainerProperties.class)
136+
@Import(TestBean.class)
137+
static class ContainerAndPropertyRegistrarConfiguration {
138+
139+
@Bean
140+
RedisContainer redisContainer() {
141+
return TestImage.container(RedisContainer.class);
142+
}
143+
144+
@Bean
145+
DynamicPropertyRegistrar redisProperties(RedisContainer container) {
146+
return (registry) -> registry.add("container.port", container::getFirstMappedPort);
147+
}
148+
149+
}
150+
80151
@ConfigurationProperties("container")
81152
record ContainerProperties(int port) {
82153
}

spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationWithSpringBootTestIntegrationTest.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,41 @@
1818

1919
import org.junit.jupiter.api.Test;
2020

21+
import org.springframework.beans.factory.annotation.Autowired;
2122
import org.springframework.boot.SpringBootConfiguration;
2223
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
2324
import org.springframework.boot.test.context.SpringBootTest;
2425
import org.springframework.boot.test.context.TestConfiguration;
2526
import org.springframework.boot.testcontainers.properties.TestcontainersPropertySourceAutoConfigurationWithSpringBootTestIntegrationTest.TestConfig;
2627
import org.springframework.context.annotation.Bean;
28+
import org.springframework.core.env.Environment;
29+
import org.springframework.test.context.DynamicPropertyRegistrar;
2730
import org.springframework.test.context.DynamicPropertyRegistry;
2831

32+
import static org.assertj.core.api.Assertions.assertThat;
33+
2934
/**
3035
* Tests for {@link TestcontainersPropertySourceAutoConfiguration} when combined with
3136
* {@link SpringBootTest @SpringBootTest}.
3237
*
3338
* @author Phillip Webb
39+
* @author Andy Wilkinson
3440
*/
35-
@SpringBootTest(classes = TestConfig.class)
41+
@SpringBootTest(classes = TestConfig.class,
42+
properties = "spring.testcontainers.dynamic-property-registry-injection=allow")
3643
class TestcontainersPropertySourceAutoConfigurationWithSpringBootTestIntegrationTest {
3744

45+
@Autowired
46+
private Environment environment;
47+
3848
@Test
39-
void injectsRegistry() {
49+
void injectsRegistryIntoBeanMethod() {
50+
assertThat(this.environment.getProperty("from.bean.method")).isEqualTo("one");
51+
}
4052

53+
@Test
54+
void callsRegistrars() {
55+
assertThat(this.environment.getProperty("from.registrar")).isEqualTo("two");
4156
}
4257

4358
@TestConfiguration
@@ -47,10 +62,15 @@ static class TestConfig {
4762

4863
@Bean
4964
String example(DynamicPropertyRegistry registry) {
50-
registry.add("test", () -> "test");
65+
registry.add("from.bean.method", () -> "one");
5166
return "Hello";
5267
}
5368

69+
@Bean
70+
DynamicPropertyRegistrar propertyRegistrar() {
71+
return (registry) -> registry.add("from.registrar", () -> "two");
72+
}
73+
5474
}
5575

5676
}

spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/ContainerFieldsImporter.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
import java.lang.reflect.Field;
2020
import java.lang.reflect.Modifier;
2121
import java.util.ArrayList;
22+
import java.util.HashSet;
2223
import java.util.List;
24+
import java.util.Set;
2325

2426
import org.testcontainers.containers.Container;
27+
import org.testcontainers.lifecycle.Startable;
2528

2629
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
2730
import org.springframework.boot.autoconfigure.container.ContainerImageMetadata;
@@ -35,12 +38,17 @@
3538
*/
3639
class ContainerFieldsImporter {
3740

38-
void registerBeanDefinitions(BeanDefinitionRegistry registry, Class<?> definitionClass) {
41+
Set<Startable> registerBeanDefinitions(BeanDefinitionRegistry registry, Class<?> definitionClass) {
42+
Set<Startable> importedContainers = new HashSet<>();
3943
for (Field field : getContainerFields(definitionClass)) {
4044
assertValid(field);
4145
Container<?> container = getContainer(field);
46+
if (container instanceof Startable startable) {
47+
importedContainers.add(startable);
48+
}
4249
registerBeanDefinition(registry, field, container);
4350
}
51+
return importedContainers;
4452
}
4553

4654
private List<Field> getContainerFields(Class<?> containersClass) {

0 commit comments

Comments
 (0)