diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/DynamicPropertySourceMethodsImporter.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/DynamicPropertySourceMethodsImporter.java index 35d72d7c72..d680f7504c 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/DynamicPropertySourceMethodsImporter.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/DynamicPropertySourceMethodsImporter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Set; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.boot.testcontainers.properties.TestcontainersPropertySource; import org.springframework.core.MethodIntrospector; import org.springframework.core.annotation.MergedAnnotations; @@ -43,16 +44,17 @@ class DynamicPropertySourceMethodsImporter { this.environment = environment; } - void registerDynamicPropertySources(Class definitionClass) { + void registerDynamicPropertySources(BeanDefinitionRegistry beanDefinitionRegistry, Class definitionClass) { Set methods = MethodIntrospector.selectMethods(definitionClass, this::isAnnotated); if (methods.isEmpty()) { return; } - DynamicPropertyRegistry registry = TestcontainersPropertySource.attach(this.environment); + DynamicPropertyRegistry dynamicPropertyRegistry = TestcontainersPropertySource.attach(this.environment, + beanDefinitionRegistry); methods.forEach((method) -> { assertValid(method); ReflectionUtils.makeAccessible(method); - ReflectionUtils.invokeMethod(method, null, registry); + ReflectionUtils.invokeMethod(method, null, dynamicPropertyRegistry); }); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/ImportTestcontainersRegistrar.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/ImportTestcontainersRegistrar.java index 9e4dc4c97d..1c2cf49725 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/ImportTestcontainersRegistrar.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/context/ImportTestcontainersRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,7 +62,7 @@ class ImportTestcontainersRegistrar implements ImportBeanDefinitionRegistrar { for (Class definitionClass : definitionClasses) { this.containerFieldsImporter.registerBeanDefinitions(registry, definitionClass); if (this.dynamicPropertySourceMethodsImporter != null) { - this.dynamicPropertySourceMethodsImporter.registerDynamicPropertySources(definitionClass); + this.dynamicPropertySourceMethodsImporter.registerDynamicPropertySources(registry, definitionClass); } } } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializer.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializer.java index 41420bbb6f..662be02a7f 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializer.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,7 +48,10 @@ public class TestcontainersLifecycleApplicationContextInitializer ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory(); applicationContext.addBeanFactoryPostProcessor(new TestcontainersLifecycleBeanFactoryPostProcessor()); TestcontainersStartup startup = TestcontainersStartup.get(applicationContext.getEnvironment()); - beanFactory.addBeanPostProcessor(new TestcontainersLifecycleBeanPostProcessor(beanFactory, startup)); + TestcontainersLifecycleBeanPostProcessor beanPostProcessor = new TestcontainersLifecycleBeanPostProcessor( + beanFactory, startup); + beanFactory.addBeanPostProcessor(beanPostProcessor); + applicationContext.addApplicationListener(beanPostProcessor); } } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleBeanPostProcessor.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleBeanPostProcessor.java index 3ec39b713a..519d5de0d6 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleBeanPostProcessor.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,6 +38,8 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; +import org.springframework.boot.testcontainers.properties.BeforeTestcontainersPropertySuppliedEvent; +import org.springframework.context.ApplicationListener; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.log.LogMessage; @@ -56,7 +58,8 @@ import org.springframework.core.log.LogMessage; * @see TestcontainersLifecycleApplicationContextInitializer */ @Order(Ordered.LOWEST_PRECEDENCE) -class TestcontainersLifecycleBeanPostProcessor implements DestructionAwareBeanPostProcessor { +class TestcontainersLifecycleBeanPostProcessor + implements DestructionAwareBeanPostProcessor, ApplicationListener { private static final Log logger = LogFactory.getLog(TestcontainersLifecycleBeanPostProcessor.class); @@ -74,9 +77,14 @@ class TestcontainersLifecycleBeanPostProcessor implements DestructionAwareBeanPo this.startup = startup; } + @Override + public void onApplicationEvent(BeforeTestcontainersPropertySuppliedEvent event) { + initializeContainers(); + } + @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (this.beanFactory.isConfigurationFrozen() && this.containersInitialized.compareAndSet(false, true)) { + if (this.beanFactory.isConfigurationFrozen()) { initializeContainers(); } if (bean instanceof Startable startableBean) { @@ -121,15 +129,17 @@ class TestcontainersLifecycleBeanPostProcessor implements DestructionAwareBeanPo } private void initializeContainers() { - logger.trace("Initializing containers"); - List beanNames = List.of(this.beanFactory.getBeanNamesForType(ContainerState.class, false, false)); - List beans = getBeans(beanNames); - if (beans != null) { - logger.trace(LogMessage.format("Initialized containers %s", beanNames)); - } - else { - logger.trace(LogMessage.format("Failed to initialize containers %s", beanNames)); - this.containersInitialized.set(false); + if (this.containersInitialized.compareAndSet(false, true)) { + logger.trace("Initializing containers"); + List beanNames = List.of(this.beanFactory.getBeanNamesForType(ContainerState.class, false, false)); + List beans = getBeans(beanNames); + if (beans != null) { + logger.trace(LogMessage.format("Initialized containers %s", beanNames)); + } + else { + logger.trace(LogMessage.format("Failed to initialize containers %s", beanNames)); + this.containersInitialized.set(false); + } } } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/BeforeTestcontainersPropertySuppliedEvent.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/BeforeTestcontainersPropertySuppliedEvent.java new file mode 100644 index 0000000000..4efe599029 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/BeforeTestcontainersPropertySuppliedEvent.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.properties; + +import java.util.function.Supplier; + +import org.springframework.context.ApplicationEvent; + +/** + * Event published just before the {@link Supplier value supplier} of a + * {@link TestcontainersPropertySource} property is called. + * + * @author Phillip Webb + * @since 3.2.2 + */ +public class BeforeTestcontainersPropertySuppliedEvent extends ApplicationEvent { + + private final String propertyName; + + BeforeTestcontainersPropertySuppliedEvent(TestcontainersPropertySource source, String propertyName) { + super(source); + this.propertyName = propertyName; + } + + /** + * Return the name of the property about to be supplied. + * @return the propertyName the property name + */ + public String getPropertyName() { + return this.propertyName; + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySource.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySource.java index 03cc093229..1994303645 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySource.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,20 @@ package org.springframework.boot.testcontainers.properties; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.Supplier; import org.testcontainers.containers.Container; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.Environment; @@ -44,6 +54,8 @@ public class TestcontainersPropertySource extends EnumerablePropertySource eventPublishers = new CopyOnWriteArraySet<>(); + TestcontainersPropertySource() { this(Collections.synchronizedMap(new LinkedHashMap<>())); } @@ -57,10 +69,20 @@ public class TestcontainersPropertySource extends EnumerablePropertySource valueSupplier = this.source.get(name); - return (valueSupplier != null) ? valueSupplier.get() : null; + return (valueSupplier != null) ? getProperty(name, valueSupplier) : null; + } + + private Object getProperty(String name, Supplier valueSupplier) { + BeforeTestcontainersPropertySuppliedEvent event = new BeforeTestcontainersPropertySuppliedEvent(this, name); + this.eventPublishers.forEach((eventPublisher) -> eventPublisher.publishEvent(event)); + return valueSupplier.get(); } @Override @@ -74,20 +96,73 @@ public class TestcontainersPropertySource extends EnumerablePropertySource new EventPublisherRegistrar(environment))); + } + return propertySource.registry; + } + + static TestcontainersPropertySource getOrAdd(ConfigurableEnvironment environment) { PropertySource propertySource = environment.getPropertySources().get(NAME); if (propertySource == null) { environment.getPropertySources().addFirst(new TestcontainersPropertySource()); - return attach(environment); + return getOrAdd(environment); } Assert.state(propertySource instanceof TestcontainersPropertySource, "Incorrect DynamicValuesPropertySource type registered"); - return ((TestcontainersPropertySource) propertySource).registry; + return ((TestcontainersPropertySource) propertySource); + } + + /** + * {@link BeanFactoryPostProcessor} to register the {@link ApplicationEventPublisher} + * to the {@link TestcontainersPropertySource}. This class is a + * {@link BeanFactoryPostProcessor} so that it is initialized as early as possible. + */ + private static class EventPublisherRegistrar implements BeanFactoryPostProcessor, ApplicationEventPublisherAware { + + static final String NAME = EventPublisherRegistrar.class.getName(); + + private final Environment environment; + + private ApplicationEventPublisher eventPublisher; + + EventPublisherRegistrar(Environment environment) { + this.environment = environment; + } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) { + this.eventPublisher = eventPublisher; + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + if (this.eventPublisher != null) { + TestcontainersPropertySource.getOrAdd((ConfigurableEnvironment) this.environment) + .addEventPublisher(this.eventPublisher); + } + } + } } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfiguration.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfiguration.java index 5c016c964e..e6219f2d2d 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfiguration.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,12 @@ package org.springframework.boot.testcontainers.properties; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; -import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.test.context.DynamicPropertyRegistry; /** @@ -28,6 +31,8 @@ import org.springframework.test.context.DynamicPropertyRegistry; * @author Phillip Webb * @since 3.1.0 */ +@AutoConfiguration +@Order(Ordered.HIGHEST_PRECEDENCE) @ConditionalOnClass(DynamicPropertyRegistry.class) public class TestcontainersPropertySourceAutoConfiguration { @@ -35,8 +40,8 @@ public class TestcontainersPropertySourceAutoConfiguration { } @Bean - DynamicPropertyRegistry dynamicPropertyRegistry(ConfigurableEnvironment environment) { - return TestcontainersPropertySource.attach(environment); + static DynamicPropertyRegistry dynamicPropertyRegistry(ConfigurableApplicationContext applicationContext) { + return TestcontainersPropertySource.attach(applicationContext); } } diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersImportWithPropertiesInjectedIntoLoadTimeWeaverAwareBeanIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersImportWithPropertiesInjectedIntoLoadTimeWeaverAwareBeanIntegrationTests.java new file mode 100644 index 0000000000..cab98edd15 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersImportWithPropertiesInjectedIntoLoadTimeWeaverAwareBeanIntegrationTests.java @@ -0,0 +1,102 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.testcontainers.lifecycle; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.testcontainers.context.ImportTestcontainers; +import org.springframework.boot.testcontainers.lifecycle.TestcontainersImportWithPropertiesInjectedIntoLoadTimeWeaverAwareBeanIntegrationTests.Containers; +import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable; +import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +import org.springframework.context.annotation.Bean; +import org.springframework.context.weaving.LoadTimeWeaverAware; +import org.springframework.instrument.classloading.LoadTimeWeaver; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author Phillip Webb + */ +@ExtendWith(SpringExtension.class) +@DirtiesContext +@DisabledIfDockerUnavailable +@ImportTestcontainers(Containers.class) +class TestcontainersImportWithPropertiesInjectedIntoLoadTimeWeaverAwareBeanIntegrationTests { + + // gh-38913 + + @Test + void starts() { + } + + @TestConfiguration + @EnableConfigurationProperties(MockDataSourceProperties.class) + static class Config { + + @Bean + MockEntityManager mockEntityManager(MockDataSourceProperties properties) { + return new MockEntityManager(); + } + + } + + static class MockEntityManager implements LoadTimeWeaverAware { + + @Override + public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) { + } + + } + + @ConfigurationProperties("spring.datasource") + public static class MockDataSourceProperties { + + private String url; + + public String getUrl() { + return this.url; + } + + public void setUrl(String url) { + this.url = url; + } + + } + + static class Containers { + + @Container + static PostgreSQLContainer container = new PostgreSQLContainer<>(DockerImageNames.postgresql()); + + @DynamicPropertySource + static void setConnectionProperties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.url", container::getJdbcUrl); + registry.add("spring.datasource.password", container::getPassword); + registry.add("spring.datasource.username", container::getUsername); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationTests.java index 723a4203f0..90103b24d7 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,9 @@ package org.springframework.boot.testcontainers.properties; +import java.util.ArrayList; +import java.util.List; + import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -25,6 +28,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleApplicationContextInitializer; import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable; import org.springframework.boot.testsupport.testcontainers.RedisContainer; +import org.springframework.context.ApplicationEvent; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -46,11 +50,16 @@ class TestcontainersPropertySourceAutoConfigurationTests { @Test void containerBeanMethodContributesProperties() { - this.contextRunner.withUserConfiguration(ContainerAndPropertiesConfiguration.class).run((context) -> { - TestBean testBean = context.getBean(TestBean.class); - RedisContainer redisContainer = context.getBean(RedisContainer.class); - assertThat(testBean.getUsingPort()).isEqualTo(redisContainer.getFirstMappedPort()); - }); + List events = new ArrayList<>(); + this.contextRunner.withUserConfiguration(ContainerAndPropertiesConfiguration.class) + .withInitializer((context) -> context.addApplicationListener(events::add)) + .run((context) -> { + TestBean testBean = context.getBean(TestBean.class); + RedisContainer redisContainer = context.getBean(RedisContainer.class); + assertThat(testBean.getUsingPort()).isEqualTo(redisContainer.getFirstMappedPort()); + assertThat(events.stream().filter(BeforeTestcontainersPropertySuppliedEvent.class::isInstance)) + .hasSize(1); + }); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceTests.java index 7958666992..4dce61ffc9 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,15 @@ package org.springframework.boot.testcontainers.properties; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.PropertySource; import org.springframework.mock.env.MockEnvironment; @@ -101,4 +106,20 @@ class TestcontainersPropertySourceTests { assertThat(p1).isSameAs(p2); } + @Test + void getPropertyPublishesEvent() { + try (GenericApplicationContext applicationContext = new GenericApplicationContext()) { + List events = new ArrayList<>(); + applicationContext.addApplicationListener(events::add); + DynamicPropertyRegistry registry = TestcontainersPropertySource.attach(applicationContext.getEnvironment(), + (BeanDefinitionRegistry) applicationContext.getBeanFactory()); + applicationContext.refresh(); + registry.add("test", () -> "spring"); + assertThat(applicationContext.getEnvironment().containsProperty("test")).isTrue(); + assertThat(events.isEmpty()); + assertThat(applicationContext.getEnvironment().getProperty("test")).isEqualTo("spring"); + assertThat(events.stream().filter(BeforeTestcontainersPropertySuppliedEvent.class::isInstance)).hasSize(1); + } + } + }