From 14bc354f7f93f7a70589496692ad3e6a187a6efd Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 1 May 2023 17:35:23 -0700 Subject: [PATCH] Apply initializer automatically for context tests Update `ServiceConnectionContextCustomizer` so that is applies the `TestcontainersLifecycleApplicationContextInitializer` to all application contexts. Closes gh-35222 --- ...ersLifecycleApplicationContextInitializer.java | 9 +++++++++ .../ServiceConnectionContextCustomizer.java | 2 ++ ...ServiceConnectionContextCustomizerFactory.java | 2 +- ...fecycleApplicationContextInitializerTests.java | 11 +++++++++++ ...ceConnectionContextCustomizerFactoryTests.java | 15 +++++++++++++-- 5 files changed, 36 insertions(+), 3 deletions(-) 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 7c05a39583..4a2b52ce2c 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 @@ -16,6 +16,8 @@ package org.springframework.boot.testcontainers.lifecycle; +import java.util.WeakHashMap; + import org.testcontainers.lifecycle.Startable; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -32,8 +34,15 @@ import org.springframework.context.ConfigurableApplicationContext; public class TestcontainersLifecycleApplicationContextInitializer implements ApplicationContextInitializer { + private static WeakHashMap applied = new WeakHashMap<>(); + @Override public void initialize(ConfigurableApplicationContext applicationContext) { + synchronized (applied) { + if (applied.put(applicationContext, Boolean.TRUE) == Boolean.TRUE) { + return; + } + } ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory(); applicationContext.addBeanFactoryPostProcessor(new TestcontainersLifecycleBeanFactoryPostProcessor()); beanFactory.addBeanPostProcessor(new TestcontainersLifecycleBeanPostProcessor(beanFactory)); diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizer.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizer.java index 71aa441933..b8d010f613 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizer.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizer.java @@ -26,6 +26,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactories; +import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.MergedContextConfiguration; @@ -58,6 +59,7 @@ class ServiceConnectionContextCustomizer implements ContextCustomizer { @Override public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { + new TestcontainersLifecycleApplicationContextInitializer().initialize(context); ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); if (beanFactory instanceof BeanDefinitionRegistry registry) { new ConnectionDetailsRegistrar(beanFactory, this.connectionDetailsFactories) diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactory.java index bba65dbab1..9e1b86d526 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactory.java @@ -49,7 +49,7 @@ class ServiceConnectionContextCustomizerFactory implements ContextCustomizerFact List configAttributes) { List> sources = new ArrayList<>(); findSources(testClass, sources); - return (sources.isEmpty()) ? null : new ServiceConnectionContextCustomizer(sources); + return new ServiceConnectionContextCustomizer(sources); } private void findSources(Class clazz, List> sources) { diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializerTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializerTests.java index d06c993f27..111216fb01 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializerTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializerTests.java @@ -24,6 +24,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; @@ -84,6 +85,16 @@ class TestcontainersLifecycleApplicationContextInitializerTests { then(container).should(never()).close(); } + @Test + void doesNotInitializeSameContextMoreThanOnce() { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + int initialNumberOfPostProcessors = applicationContext.getBeanFactoryPostProcessors().size(); + for (int i = 0; i < 10; i++) { + new TestcontainersLifecycleApplicationContextInitializer().initialize(applicationContext); + } + assertThat(applicationContext.getBeanFactoryPostProcessors()).hasSize(initialNumberOfPostProcessors + 1); + } + private AnnotationConfigApplicationContext createApplicationContext(Startable container) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); new TestcontainersLifecycleApplicationContextInitializer().initialize(applicationContext); diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactoryTests.java index 2bff6cf289..dd7b0a681f 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactoryTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionContextCustomizerFactoryTests.java @@ -24,8 +24,13 @@ import org.junit.jupiter.api.Test; import org.testcontainers.containers.Container; import org.testcontainers.containers.GenericContainer; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.MergedContextConfiguration; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.Mockito.mock; /** * Tests for {@link ServiceConnectionContextCustomizerFactory}. @@ -39,8 +44,14 @@ class ServiceConnectionContextCustomizerFactoryTests { private final ServiceConnectionContextCustomizerFactory factory = new ServiceConnectionContextCustomizerFactory(); @Test - void createContextCustomizerWhenNoServiceConnectionsReturnsNull() { - assertThat(this.factory.createContextCustomizer(NoServiceConnections.class, null)).isNull(); + void createContextCustomizerWhenNoServiceConnectionsReturnsCustomizerToApplyInitializer() { + ContextCustomizer customizer = this.factory.createContextCustomizer(NoServiceConnections.class, null); + assertThat(customizer).isNotNull(); + GenericApplicationContext context = new GenericApplicationContext(); + int initialNumberOfPostProcessors = context.getBeanFactoryPostProcessors().size(); + MergedContextConfiguration mergedConfig = mock(MergedContextConfiguration.class); + customizer.customizeContext(context, mergedConfig); + assertThat(context.getBeanFactoryPostProcessors()).hasSize(initialNumberOfPostProcessors + 1); } @Test