Skip to content

Commit 3f7bf7d

Browse files
committed
Modify SpringApplication Environment rather than setting it
Update `SpringBootContextLoader` so that when possible the `SpringApplication` remains in control of creating the `Environment` instance. Prior to this commit, we would always create the `Environment` in the `SpringBootContextLoader` and then call `setEnvironment` on the `SpringApplication`. This meant that the `ApplicationEnvironment` classes were not used and that `isCustomEnvironment` was set to `true` so no conversion was applied. With the updated code, an `ApplicationListener` is used to mutate the `Environment` instance and add the required test property sources. Fixes gh-29169
1 parent e8cbec0 commit 3f7bf7d

File tree

3 files changed

+87
-18
lines changed

3 files changed

+87
-18
lines changed

spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootContextLoader.java

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.Arrays;
21+
import java.util.Collections;
2122
import java.util.List;
2223

2324
import org.springframework.beans.BeanUtils;
2425
import org.springframework.boot.ApplicationContextFactory;
26+
import org.springframework.boot.DefaultPropertiesPropertySource;
2527
import org.springframework.boot.SpringApplication;
2628
import org.springframework.boot.WebApplicationType;
29+
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
2730
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
2831
import org.springframework.boot.test.mock.web.SpringBootMockServletContext;
2932
import org.springframework.boot.test.util.TestPropertyValues;
@@ -32,14 +35,18 @@
3235
import org.springframework.boot.web.servlet.support.ServletContextApplicationContextInitializer;
3336
import org.springframework.context.ApplicationContext;
3437
import org.springframework.context.ApplicationContextInitializer;
38+
import org.springframework.context.ApplicationListener;
3539
import org.springframework.context.ConfigurableApplicationContext;
3640
import org.springframework.core.Ordered;
41+
import org.springframework.core.PriorityOrdered;
3742
import org.springframework.core.SpringVersion;
3843
import org.springframework.core.annotation.MergedAnnotations;
3944
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
4045
import org.springframework.core.annotation.Order;
46+
import org.springframework.core.env.CommandLinePropertySource;
4147
import org.springframework.core.env.ConfigurableEnvironment;
42-
import org.springframework.core.env.Environment;
48+
import org.springframework.core.env.MutablePropertySources;
49+
import org.springframework.core.env.PropertySource;
4350
import org.springframework.core.env.StandardEnvironment;
4451
import org.springframework.core.io.DefaultResourceLoader;
4552
import org.springframework.core.io.ResourceLoader;
@@ -53,6 +60,7 @@
5360
import org.springframework.test.context.web.WebMergedContextConfiguration;
5461
import org.springframework.util.Assert;
5562
import org.springframework.util.ObjectUtils;
63+
import org.springframework.util.ReflectionUtils;
5664
import org.springframework.util.StringUtils;
5765
import org.springframework.web.context.support.GenericWebApplicationContext;
5866

@@ -81,6 +89,9 @@
8189
*/
8290
public class SpringBootContextLoader extends AbstractContextLoader {
8391

92+
private static final String[] PRIORITY_PROPERTY_SOURCES = { "configurationProperties",
93+
DefaultPropertiesPropertySource.NAME, CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME };
94+
8495
@Override
8596
public ApplicationContext loadContext(MergedContextConfiguration config) throws Exception {
8697
Class<?>[] configClasses = config.getClasses();
@@ -111,23 +122,49 @@ else if (config instanceof ReactiveWebMergedContextConfiguration) {
111122
application.setWebApplicationType(WebApplicationType.NONE);
112123
}
113124
application.setInitializers(initializers);
114-
ConfigurableEnvironment environment = getEnvironment();
115-
setActiveProfiles(environment, config.getActiveProfiles());
125+
boolean customEnvironent = ReflectionUtils.findMethod(getClass(), "getEnvironment")
126+
.getDeclaringClass() != SpringBootContextLoader.class;
127+
if (customEnvironent) {
128+
ConfigurableEnvironment environment = getEnvironment();
129+
prepareEnvironment(config, application, environment, false);
130+
application.setEnvironment(environment);
131+
}
132+
else {
133+
application.addListeners(new PrepareEnvironmentListener(config));
134+
}
135+
String[] args = SpringBootTestArgs.get(config.getContextCustomizers());
136+
return application.run(args);
137+
}
138+
139+
private void prepareEnvironment(MergedContextConfiguration config, SpringApplication application,
140+
ConfigurableEnvironment environment, boolean applicationEnvironment) {
141+
MutablePropertySources propertySources = environment.getPropertySources();
142+
List<PropertySource<?>> priorityPropertySources = new ArrayList<>();
143+
if (applicationEnvironment) {
144+
for (String priorityPropertySourceName : PRIORITY_PROPERTY_SOURCES) {
145+
PropertySource<?> priorityPropertySource = propertySources.get(priorityPropertySourceName);
146+
if (priorityPropertySource != null) {
147+
priorityPropertySources.add(priorityPropertySource);
148+
propertySources.remove(priorityPropertySourceName);
149+
}
150+
}
151+
}
152+
setActiveProfiles(environment, config.getActiveProfiles(), applicationEnvironment);
116153
ResourceLoader resourceLoader = (application.getResourceLoader() != null) ? application.getResourceLoader()
117154
: new DefaultResourceLoader(null);
118155
TestPropertySourceUtils.addPropertiesFilesToEnvironment(environment, resourceLoader,
119156
config.getPropertySourceLocations());
120157
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(environment, getInlinedProperties(config));
121-
application.setEnvironment(environment);
122-
String[] args = SpringBootTestArgs.get(config.getContextCustomizers());
123-
return application.run(args);
158+
Collections.reverse(priorityPropertySources);
159+
priorityPropertySources.forEach(propertySources::addFirst);
124160
}
125161

126-
private void setActiveProfiles(ConfigurableEnvironment environment, String[] profiles) {
162+
private void setActiveProfiles(ConfigurableEnvironment environment, String[] profiles,
163+
boolean applicationEnvironment) {
127164
if (ObjectUtils.isEmpty(profiles)) {
128165
return;
129166
}
130-
if (!(environment instanceof TestEnvironment)) {
167+
if (!applicationEnvironment) {
131168
environment.setActiveProfiles(profiles);
132169
}
133170
String[] pairs = new String[profiles.length];
@@ -152,7 +189,7 @@ protected SpringApplication getSpringApplication() {
152189
* @return a {@link ConfigurableEnvironment} instance
153190
*/
154191
protected ConfigurableEnvironment getEnvironment() {
155-
return new TestEnvironment();
192+
return new StandardEnvironment();
156193
}
157194

158195
protected String[] getInlinedProperties(MergedContextConfiguration config) {
@@ -304,19 +341,25 @@ public void initialize(ConfigurableApplicationContext applicationContext) {
304341
}
305342

306343
/**
307-
* Side-effect free {@link Environment} that doesn't set profiles directly from
308-
* properties.
344+
* {@link ApplicationListener} used to prepare the application created environment.
309345
*/
310-
static class TestEnvironment extends StandardEnvironment {
346+
private class PrepareEnvironmentListener
347+
implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, PriorityOrdered {
348+
349+
private final MergedContextConfiguration config;
350+
351+
PrepareEnvironmentListener(MergedContextConfiguration config) {
352+
this.config = config;
353+
}
311354

312355
@Override
313-
protected String doGetActiveProfilesProperty() {
314-
return null;
356+
public int getOrder() {
357+
return Ordered.HIGHEST_PRECEDENCE;
315358
}
316359

317360
@Override
318-
protected String doGetDefaultProfilesProperty() {
319-
return null;
361+
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
362+
prepareEnvironment(this.config, event.getSpringApplication(), event.getEnvironment(), true);
320363
}
321364

322365
}

spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,24 @@
1616

1717
package org.springframework.boot.test.context;
1818

19+
import java.util.List;
1920
import java.util.Map;
21+
import java.util.stream.Collectors;
2022

2123
import org.junit.jupiter.api.Disabled;
2224
import org.junit.jupiter.api.Test;
2325

2426
import org.springframework.boot.test.util.TestPropertyValues;
2527
import org.springframework.context.ApplicationContext;
2628
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.core.env.ConfigurableEnvironment;
30+
import org.springframework.core.env.PropertySource;
2731
import org.springframework.core.env.StandardEnvironment;
2832
import org.springframework.test.context.ActiveProfiles;
2933
import org.springframework.test.context.MergedContextConfiguration;
3034
import org.springframework.test.context.TestContext;
3135
import org.springframework.test.context.TestContextManager;
36+
import org.springframework.test.context.TestPropertySource;
3237
import org.springframework.test.context.support.TestPropertySourceUtils;
3338
import org.springframework.test.util.ReflectionTestUtils;
3439

@@ -127,6 +132,20 @@ void testPropertyValuesShouldTakePrecedenceWhenInlinedPropertiesPresentAndProfil
127132
assertThat(environment.getPropertySources().get("active-test-profiles")).isNotNull();
128133
}
129134

135+
@Test
136+
void propertySourceOrdering() throws Exception {
137+
TestContext context = new ExposedTestContextManager(PropertySourceOrdering.class).getExposedTestContext();
138+
ConfigurableEnvironment environment = (ConfigurableEnvironment) context.getApplicationContext()
139+
.getEnvironment();
140+
List<String> names = environment.getPropertySources().stream().map(PropertySource::getName)
141+
.collect(Collectors.toList());
142+
String last = names.remove(names.size() - 1);
143+
assertThat(names).containsExactly("configurationProperties", "commandLineArgs", "Inlined Test Properties",
144+
"servletConfigInitParams", "servletContextInitParams", "systemProperties", "systemEnvironment",
145+
"random");
146+
assertThat(last).startsWith("Config resource");
147+
}
148+
130149
private String[] getActiveProfiles(Class<?> testClass) {
131150
TestContext testContext = new ExposedTestContextManager(testClass).getExposedTestContext();
132151
ApplicationContext applicationContext = testContext.getApplicationContext();
@@ -198,6 +217,12 @@ static class ActiveProfileWithInlinedProperties {
198217

199218
}
200219

220+
@SpringBootTest(classes = Config.class, args = "args", properties = "one=1")
221+
@TestPropertySource(properties = "two=2")
222+
static class PropertySourceOrdering {
223+
224+
}
225+
201226
@Configuration(proxyBeanMethods = false)
202227
static class Config {
203228

spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-profile/src/test/java/smoketest/profile/ActiveProfilesTests.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
2121
import org.springframework.beans.factory.annotation.Autowired;
2222
import org.springframework.boot.env.EnvironmentPostProcessor;
2323
import org.springframework.boot.test.context.SpringBootTest;
24+
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
2425
import org.springframework.core.env.Environment;
2526
import org.springframework.test.context.ActiveProfiles;
2627

@@ -32,7 +33,7 @@
3233
*
3334
* @author Madhura Bhave
3435
*/
35-
@SpringBootTest(properties = "enableEnvironmentPostProcessor=true") // gh-28530
36+
@SpringBootTest(webEnvironment = WebEnvironment.NONE, properties = "enableEnvironmentPostProcessor=true") // gh-28530
3637
@ActiveProfiles("hello")
3738
class ActiveProfilesTests {
3839

0 commit comments

Comments
 (0)