Skip to content

Commit

Permalink
Honor final web application type when creating context in tests
Browse files Browse the repository at this point in the history
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
  • Loading branch information
wilkinsona committed Apr 14, 2022
1 parent dfc0f21 commit c8de843
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
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;

/**
Expand Down Expand Up @@ -113,14 +114,21 @@ public ApplicationContext loadContext(MergedContextConfiguration config) throws
}
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;
Expand Down Expand Up @@ -285,14 +293,38 @@ void configure(MergedContextConfiguration configuration, SpringApplication appli
List<ApplicationContextInitializer<?>> initializers) {
WebMergedContextConfiguration webConfiguration = (WebMergedContextConfiguration) configuration;
addMockServletContext(initializers, webConfiguration);
application.setApplicationContextFactory((webApplicationType) -> new GenericWebApplicationContext());
}

private void addMockServletContext(List<ApplicationContextInitializer<?>> 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<ConfigurableApplicationContext> {

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);
}
}

}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
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;
Expand All @@ -36,6 +37,7 @@
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;

Expand Down Expand Up @@ -146,6 +148,20 @@ void propertySourceOrdering() throws Exception {
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();
Expand Down Expand Up @@ -228,6 +244,16 @@ static class Config {

}

@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}.
*/
Expand Down

0 comments on commit c8de843

Please sign in to comment.