From c8de843d8549b584d21324258b0d3926ac105410 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 14 Apr 2022 12:26:07 +0100 Subject: [PATCH] Honor final web application type when creating context in tests Previously, SpringBootContextLoader would configure its SpringApplication with an ApplicationContextFactory that ignores the WebApplicationType with which its called and instead returns a hard-coded type of context based on the type of the MergedContextConfiguration. This hard-coding would result in the wrong type of context being used if a subsequent change was made to the application's WebApplicationType, for example due to binding of the spring.main.web-application-type configuration property. This commit updates SpringBootContextLoader to configure SpringApplication with an ApplicationContextFactory that takes the WebApplicationType with which it is called into consideration. Fixes gh-29170 --- .../test/context/SpringBootContextLoader.java | 44 ++++++++++++++++--- .../context/SpringBootContextLoaderTests.java | 26 +++++++++++ 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootContextLoader.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootContextLoader.java index 95b27ca8ce..613e9c33e0 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootContextLoader.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootContextLoader.java @@ -62,6 +62,7 @@ import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; +import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.support.GenericWebApplicationContext; /** @@ -113,14 +114,21 @@ public class SpringBootContextLoader extends AbstractContextLoader { } else if (config instanceof ReactiveWebMergedContextConfiguration) { application.setWebApplicationType(WebApplicationType.REACTIVE); - if (!isEmbeddedWebEnvironment(config)) { - application.setApplicationContextFactory( - ApplicationContextFactory.of(GenericReactiveWebApplicationContext::new)); - } } else { application.setWebApplicationType(WebApplicationType.NONE); } + application.setApplicationContextFactory((type) -> { + if (type != WebApplicationType.NONE && !isEmbeddedWebEnvironment(config)) { + if (type == WebApplicationType.REACTIVE) { + return new GenericReactiveWebApplicationContext(); + } + else if (type == WebApplicationType.SERVLET) { + return new GenericWebApplicationContext(); + } + } + return ApplicationContextFactory.DEFAULT.create(type); + }); application.setInitializers(initializers); boolean customEnvironent = ReflectionUtils.findMethod(getClass(), "getEnvironment") .getDeclaringClass() != SpringBootContextLoader.class; @@ -285,14 +293,38 @@ public class SpringBootContextLoader extends AbstractContextLoader { List> initializers) { WebMergedContextConfiguration webConfiguration = (WebMergedContextConfiguration) configuration; addMockServletContext(initializers, webConfiguration); - application.setApplicationContextFactory((webApplicationType) -> new GenericWebApplicationContext()); } private void addMockServletContext(List> initializers, WebMergedContextConfiguration webConfiguration) { SpringBootMockServletContext servletContext = new SpringBootMockServletContext( webConfiguration.getResourceBasePath()); - initializers.add(0, new ServletContextApplicationContextInitializer(servletContext, true)); + initializers.add(0, new DefensiveWebApplicationContextInitializer( + new ServletContextApplicationContextInitializer(servletContext, true))); + } + + /** + * Decorator for {@link ServletContextApplicationContextInitializer} that prevents + * a failure when the context type is not as was predicted when the initializer + * was registered. This can occur when spring.main.web-application-type is set to + * something other than servlet. + */ + private static final class DefensiveWebApplicationContextInitializer + implements ApplicationContextInitializer { + + private final ServletContextApplicationContextInitializer delegate; + + private DefensiveWebApplicationContextInitializer(ServletContextApplicationContextInitializer delegate) { + this.delegate = delegate; + } + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + if (applicationContext instanceof ConfigurableWebApplicationContext) { + this.delegate.initialize((ConfigurableWebApplicationContext) applicationContext); + } + } + } } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java index c0554f6799..42e0b42566 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; @@ -36,6 +37,7 @@ import org.springframework.test.context.TestContextManager; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.support.TestPropertySourceUtils; import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.context.WebApplicationContext; import static org.assertj.core.api.Assertions.assertThat; @@ -146,6 +148,20 @@ class SpringBootContextLoaderTests { assertThat(last).startsWith("Config resource"); } + @Test + void whenEnvironmentChangesWebApplicationTypeToNoneThenContextTypeChangesAccordingly() { + TestContext context = new ExposedTestContextManager(ChangingWebApplicationTypeToNone.class) + .getExposedTestContext(); + assertThat(context.getApplicationContext()).isNotInstanceOf(WebApplicationContext.class); + } + + @Test + void whenEnvironmentChangesWebApplicationTypeToReactiveThenContextTypeChangesAccordingly() { + TestContext context = new ExposedTestContextManager(ChangingWebApplicationTypeToReactive.class) + .getExposedTestContext(); + assertThat(context.getApplicationContext()).isInstanceOf(GenericReactiveWebApplicationContext.class); + } + private String[] getActiveProfiles(Class testClass) { TestContext testContext = new ExposedTestContextManager(testClass).getExposedTestContext(); ApplicationContext applicationContext = testContext.getApplicationContext(); @@ -228,6 +244,16 @@ class SpringBootContextLoaderTests { } + @SpringBootTest(classes = Config.class, args = "--spring.main.web-application-type=none") + static class ChangingWebApplicationTypeToNone { + + } + + @SpringBootTest(classes = Config.class, args = "--spring.main.web-application-type=reactive") + static class ChangingWebApplicationTypeToReactive { + + } + /** * {@link TestContextManager} which exposes the {@link TestContext}. */