Ensure containers are started before binding datasource properties
Update `TestcontainersLifecycleBeanPostProcessor` so that containers are now initialized either on the first `postProcessAfterInitialization` call with a frozen configuration or just before a test container property is supplied. Prior to this commit, it was assumed that the first post-process call after the configuration was frozen was suitably early to initialize the containers. This turns out to not be no always the case. Specifically, in the `finishBeanFactoryInitialization` method of `AbstractApplicationContext` we see that `LoadTimeWeaverAware` beans are obtained before the configuration is frozen. One such bean is `DefaultPersistenceUnitManager` which is likely to need datasource properties that will require a started container. To fix the problem, the `TestcontainersPropertySource` now publishes a `BeforeTestcontainersPropertySuppliedEvent` to the ApplicationContext just before any value is supplied. By listening for this event, we can ensure that containers are initialized and started before any dynamic property is read. Fixes gh-38913
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<ApplicationEvent> 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)
|
||||
|
||||
@@ -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<ApplicationEvent> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user