Skip to content

Commit 393c17b

Browse files
committed
Support @SpringBootTest with @ContextHierarchy
Update SpringBootTestContextBootstrapper to detect `@ContextHierarchy` annotations so that only the last child creates a `WebApplicationContext`. Prior to this commit a context hierarchy would start two embedded web servers which is inconsistent with the `SpringApplicationBuilder` behavior. Fixes gh-8311
1 parent b9be0e3 commit 393c17b

File tree

2 files changed

+107
-2
lines changed

2 files changed

+107
-2
lines changed

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

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2016 the original author or authors.
2+
* Copyright 2012-2017 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.
@@ -19,6 +19,7 @@
1919
import java.lang.annotation.Annotation;
2020
import java.util.ArrayList;
2121
import java.util.Arrays;
22+
import java.util.HashSet;
2223
import java.util.List;
2324
import java.util.Set;
2425

@@ -28,9 +29,12 @@
2829
import org.springframework.boot.SpringBootConfiguration;
2930
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
3031
import org.springframework.core.annotation.AnnotatedElementUtils;
32+
import org.springframework.core.annotation.AnnotationUtils;
3133
import org.springframework.core.env.Environment;
3234
import org.springframework.core.io.support.SpringFactoriesLoader;
35+
import org.springframework.test.context.ContextConfiguration;
3336
import org.springframework.test.context.ContextConfigurationAttributes;
37+
import org.springframework.test.context.ContextHierarchy;
3438
import org.springframework.test.context.ContextLoader;
3539
import org.springframework.test.context.MergedContextConfiguration;
3640
import org.springframework.test.context.TestContext;
@@ -137,7 +141,7 @@ protected MergedContextConfiguration processMergedContextConfiguration(
137141
propertySourceProperties
138142
.toArray(new String[propertySourceProperties.size()]));
139143
WebEnvironment webEnvironment = getWebEnvironment(mergedConfig.getTestClass());
140-
if (webEnvironment != null) {
144+
if (webEnvironment != null && isWebEnvironmentSupported(mergedConfig)) {
141145
if (webEnvironment.isEmbedded() || (webEnvironment == WebEnvironment.MOCK
142146
&& hasWebEnvironmentClasses())) {
143147
WebAppConfiguration webAppConfiguration = AnnotatedElementUtils
@@ -152,6 +156,32 @@ && hasWebEnvironmentClasses())) {
152156
return mergedConfig;
153157
}
154158

159+
private boolean isWebEnvironmentSupported(MergedContextConfiguration mergedConfig) {
160+
Class<?> testClass = mergedConfig.getTestClass();
161+
ContextHierarchy hierarchy = AnnotationUtils.getAnnotation(testClass,
162+
ContextHierarchy.class);
163+
if (hierarchy == null || hierarchy.value().length == 0) {
164+
return true;
165+
}
166+
ContextConfiguration[] configurations = hierarchy.value();
167+
return isFromConfiguration(mergedConfig,
168+
configurations[configurations.length - 1]);
169+
}
170+
171+
private boolean isFromConfiguration(MergedContextConfiguration candidateConfig,
172+
ContextConfiguration configuration) {
173+
ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(
174+
candidateConfig.getTestClass(), configuration);
175+
Set<Class<?>> configurationClasses = new HashSet<Class<?>>(
176+
Arrays.asList(attributes.getClasses()));
177+
for (Class<?> candidate : candidateConfig.getClasses()) {
178+
if (configurationClasses.contains(candidate)) {
179+
return true;
180+
}
181+
}
182+
return false;
183+
}
184+
155185
private boolean hasWebEnvironmentClasses() {
156186
for (String className : WEB_ENVIRONMENT_CLASSES) {
157187
if (!ClassUtils.isPresent(className, null)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.test.context;
18+
19+
import org.junit.Test;
20+
import org.junit.runner.RunWith;
21+
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.boot.test.context.AbstractSpringBootTestEmbeddedWebEnvironmentTests.AbstractConfig;
24+
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
25+
import org.springframework.boot.test.context.SpringBootTestWebEnvironmentContextHierarchyTests.ChildConfiguration;
26+
import org.springframework.boot.test.context.SpringBootTestWebEnvironmentContextHierarchyTests.ParentConfiguration;
27+
import org.springframework.context.ApplicationContext;
28+
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.test.annotation.DirtiesContext;
30+
import org.springframework.test.context.ContextConfiguration;
31+
import org.springframework.test.context.ContextHierarchy;
32+
import org.springframework.test.context.junit4.SpringRunner;
33+
import org.springframework.web.bind.annotation.RestController;
34+
import org.springframework.web.context.WebApplicationContext;
35+
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
36+
37+
import static org.assertj.core.api.Assertions.assertThat;
38+
39+
/**
40+
* Tests for {@link SpringBootTest} configured with {@link WebEnvironment#DEFINED_PORT}.
41+
*
42+
* @author Phillip Webb
43+
* @author Andy Wilkinson
44+
*/
45+
@RunWith(SpringRunner.class)
46+
@DirtiesContext
47+
@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT, properties = {
48+
"server.port=0", "value=123" })
49+
@ContextHierarchy({ @ContextConfiguration(classes = ParentConfiguration.class),
50+
@ContextConfiguration(classes = ChildConfiguration.class) })
51+
public class SpringBootTestWebEnvironmentContextHierarchyTests {
52+
53+
@Autowired
54+
private ApplicationContext context;
55+
56+
@Test
57+
public void testShouldOnlyStartSingleServer() throws Exception {
58+
ApplicationContext parent = this.context.getParent();
59+
assertThat(this.context).isInstanceOf(WebApplicationContext.class);
60+
assertThat(parent).isNotInstanceOf(WebApplicationContext.class);
61+
}
62+
63+
@Configuration
64+
protected static class ParentConfiguration extends AbstractConfig {
65+
66+
}
67+
68+
@Configuration
69+
@EnableWebMvc
70+
@RestController
71+
protected static class ChildConfiguration extends AbstractConfig {
72+
73+
}
74+
75+
}

0 commit comments

Comments
 (0)