Skip to content

Commit 33b9bd7

Browse files
committed
Proper support for Root WAC in Spring MVC Test
The modifications to DefaultMockMvcBuilder performed in conjunction with SPR-12553 introduced a breaking change: the WebApplicationContext supplied to DefaultMockMvcBuilder's constructor was *always* stored in the ServletContext as the root WebApplicationContext, overwriting a root WebApplicationContext that had been set by the user or by the Spring TestContext Framework (TCF) -- for example, in AbstractGenericWebContextLoader. Consequently, the changes in SPR-12553 cause tests that use @ContextHierarchy to fail if web components rely on the correct WebApplicationContext being stored under the WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE key. This commit reverts the breaking changes introduced in SPR-12553: if the root WebApplicationContext has already been set in the ServletContext of the WebApplicationContext supplied to DefaultMockMvcBuilder, no action is taken. Furthermore, this commit introduces new code to address the initial intent of SPR-12553. Specifically, if the root WebApplicationContext has NOT been set in the ServletContext of the WebApplicationContext supplied to DefaultMockMvcBuilder, the application context hierarchy will be traversed in search of the root WebApplicationContext, and the root WebApplicationContext will then be stored under the ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE key. Issue: SPR-13075, SPR-12553 (cherry picked from commit f6d2fe4)
1 parent bccdefd commit 33b9bd7

File tree

4 files changed

+171
-26
lines changed

4 files changed

+171
-26
lines changed

spring-test/src/main/java/org/springframework/test/web/servlet/setup/DefaultMockMvcBuilder.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -18,16 +18,25 @@
1818

1919
import javax.servlet.ServletContext;
2020

21+
import org.springframework.context.ApplicationContext;
2122
import org.springframework.util.Assert;
2223
import org.springframework.web.context.WebApplicationContext;
24+
import org.springframework.web.context.support.WebApplicationContextUtils;
2325

2426
/**
25-
* An concrete implementation of {@link AbstractMockMvcBuilder} that simply
26-
* provides the WebApplicationContext given to it as a constructor argument.
27+
* A concrete implementation of {@link AbstractMockMvcBuilder} that provides
28+
* the {@link WebApplicationContext} supplied to it as a constructor argument.
29+
*
30+
* <p>In addition, if the {@link ServletContext} in the supplied
31+
* {@code WebApplicationContext} does not contain an entry for the
32+
* {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
33+
* key, the root {@code WebApplicationContext} will be detected and stored
34+
* in the {@code ServletContext} under the
35+
* {@code ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} key.
2736
*
2837
* @author Rossen Stoyanchev
2938
* @author Rob Winch
30-
* @author Sebastien Deleuze
39+
* @author Sam Brannen
3140
* @since 3.2
3241
*/
3342
public class DefaultMockMvcBuilder extends AbstractMockMvcBuilder<DefaultMockMvcBuilder> {
@@ -45,11 +54,25 @@ protected DefaultMockMvcBuilder(WebApplicationContext webAppContext) {
4554
this.webAppContext = webAppContext;
4655
}
4756

48-
4957
@Override
5058
protected WebApplicationContext initWebAppContext() {
59+
5160
ServletContext servletContext = this.webAppContext.getServletContext();
52-
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.webAppContext);
61+
ApplicationContext rootWac = WebApplicationContextUtils.getWebApplicationContext(servletContext);
62+
63+
if (rootWac == null) {
64+
rootWac = this.webAppContext;
65+
ApplicationContext parent = this.webAppContext.getParent();
66+
while (parent != null) {
67+
if (parent instanceof WebApplicationContext && !(parent.getParent() instanceof WebApplicationContext)) {
68+
rootWac = parent;
69+
break;
70+
}
71+
parent = parent.getParent();
72+
}
73+
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, rootWac);
74+
}
75+
5376
return this.webAppContext;
5477
}
5578

spring-test/src/main/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,20 @@
6464
import org.springframework.web.servlet.view.InternalResourceViewResolver;
6565

6666
/**
67-
* A MockMvcBuilder that accepts {@code @Controller} registrations thus allowing
68-
* full control over the instantiation and the initialization of controllers and
69-
* their dependencies similar to plain unit tests, and also making it possible
70-
* to test one controller at a time.
67+
* A {@code MockMvcBuilder} that accepts {@code @Controller} registrations
68+
* thus allowing full control over the instantiation and initialization of
69+
* controllers and their dependencies similar to plain unit tests, and also
70+
* making it possible to test one controller at a time.
7171
*
7272
* <p>This builder creates the minimum infrastructure required by the
7373
* {@link DispatcherServlet} to serve requests with annotated controllers and
74-
* also provides methods to customize it. The resulting configuration and
75-
* customizations possible are equivalent to using the MVC Java config except
74+
* also provides methods for customization. The resulting configuration and
75+
* customization options are equivalent to using MVC Java config except
7676
* using builder style methods.
7777
*
7878
* <p>To configure view resolution, either select a "fixed" view to use for every
79-
* performed request (see {@link #setSingleView(View)}) or provide a list of
80-
* {@code ViewResolver}'s, see {@link #setViewResolvers(ViewResolver...)}.
79+
* request performed (see {@link #setSingleView(View)}) or provide a list of
80+
* {@code ViewResolver}s (see {@link #setViewResolvers(ViewResolver...)}).
8181
*
8282
* @author Rossen Stoyanchev
8383
* @since 3.2

spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/JavaConfigTests.java

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -16,12 +16,16 @@
1616

1717
package org.springframework.test.web.servlet.samples.context;
1818

19+
import javax.servlet.ServletContext;
20+
1921
import org.junit.Before;
2022
import org.junit.Test;
2123
import org.junit.runner.RunWith;
24+
2225
import org.mockito.Mockito;
2326

2427
import org.springframework.beans.factory.annotation.Autowired;
28+
import org.springframework.context.ApplicationContext;
2529
import org.springframework.context.annotation.Bean;
2630
import org.springframework.context.annotation.Configuration;
2731
import org.springframework.http.MediaType;
@@ -43,6 +47,7 @@
4347
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
4448
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
4549

50+
import static org.junit.Assert.*;
4651
import static org.mockito.BDDMockito.*;
4752
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
4853
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
@@ -69,12 +74,16 @@ public class JavaConfigTests {
6974
@Autowired
7075
private PersonDao personDao;
7176

77+
@Autowired
78+
private PersonController personController;
79+
7280
private MockMvc mockMvc;
7381

7482

7583
@Before
7684
public void setup() {
7785
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
86+
verifyRootWacSupport();
7887
given(this.personDao.getPerson(5L)).willReturn(new Person("Joe"));
7988
}
8089

@@ -88,9 +97,38 @@ public void person() throws Exception {
8897

8998
@Test
9099
public void tilesDefinitions() throws Exception {
91-
this.mockMvc.perform(get("/"))//
92-
.andExpect(status().isOk())//
93-
.andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp"));
100+
this.mockMvc.perform(get("/"))
101+
.andExpect(status().isOk())
102+
.andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp"));
103+
}
104+
105+
/**
106+
* Verify that the breaking change introduced in <a
107+
* href="https://jira.spring.io/browse/SPR-12553">SPR-12553</a> has been reverted.
108+
*
109+
* <p>This code has been copied from
110+
* {@link org.springframework.test.context.hierarchies.web.ControllerIntegrationTests}.
111+
*
112+
* @see org.springframework.test.context.hierarchies.web.ControllerIntegrationTests#verifyRootWacSupport()
113+
*/
114+
private void verifyRootWacSupport() {
115+
assertNotNull(personDao);
116+
assertNotNull(personController);
117+
118+
ApplicationContext parent = wac.getParent();
119+
assertNotNull(parent);
120+
assertTrue(parent instanceof WebApplicationContext);
121+
WebApplicationContext root = (WebApplicationContext) parent;
122+
assertFalse(root.getBeansOfType(String.class).containsKey("bar"));
123+
124+
ServletContext childServletContext = wac.getServletContext();
125+
assertNotNull(childServletContext);
126+
ServletContext rootServletContext = root.getServletContext();
127+
assertNotNull(rootServletContext);
128+
assertSame(childServletContext, rootServletContext);
129+
130+
assertSame(root, rootServletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE));
131+
assertSame(root, childServletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE));
94132
}
95133

96134

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
55
* the License. You may obtain a copy of the License at
@@ -12,28 +12,112 @@
1212
*/
1313
package org.springframework.test.web.servlet.setup;
1414

15+
import org.junit.Rule;
1516
import org.junit.Test;
17+
import org.junit.rules.ExpectedException;
1618

19+
import org.springframework.context.support.StaticApplicationContext;
1720
import org.springframework.mock.web.MockServletContext;
21+
import org.springframework.web.context.WebApplicationContext;
22+
import org.springframework.web.context.support.StaticWebApplicationContext;
1823
import org.springframework.web.context.support.WebApplicationContextUtils;
1924

25+
import static org.hamcrest.CoreMatchers.*;
2026
import static org.junit.Assert.*;
27+
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
2128

2229
/**
2330
* Tests for {@link DefaultMockMvcBuilder}.
2431
*
2532
* @author Rob Winch
2633
* @author Sebastien Deleuze
34+
* @author Sam Brannen
2735
*/
2836
public class DefaultMockMvcBuilderTests {
2937

30-
@Test // SPR-12553
31-
public void applicationContextAttribute() {
32-
MockServletContext servletContext = new MockServletContext();
33-
StubWebApplicationContext wac = new StubWebApplicationContext(servletContext);
34-
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(wac);
35-
assertEquals(builder.initWebAppContext(), WebApplicationContextUtils
36-
.getRequiredWebApplicationContext(servletContext));
38+
private final MockServletContext servletContext = new MockServletContext();
39+
40+
@Rule
41+
public final ExpectedException exception = ExpectedException.none();
42+
43+
44+
@Test
45+
public void webAppContextSetupWithNullWac() {
46+
exception.expect(IllegalArgumentException.class);
47+
exception.expectMessage(equalTo("WebApplicationContext is required"));
48+
webAppContextSetup(null);
49+
}
50+
51+
@Test
52+
public void webAppContextSetupWithNullServletContext() {
53+
exception.expect(IllegalArgumentException.class);
54+
exception.expectMessage(equalTo("WebApplicationContext must have a ServletContext"));
55+
webAppContextSetup(new StubWebApplicationContext(null));
56+
}
57+
58+
/**
59+
* See SPR-12553 and SPR-13075.
60+
*/
61+
@Test
62+
public void rootWacServletContainerAttributePreviouslySet() {
63+
StubWebApplicationContext child = new StubWebApplicationContext(this.servletContext);
64+
this.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, child);
65+
66+
DefaultMockMvcBuilder builder = webAppContextSetup(child);
67+
assertSame(builder.initWebAppContext(),
68+
WebApplicationContextUtils.getRequiredWebApplicationContext(this.servletContext));
69+
}
70+
71+
/**
72+
* See SPR-12553 and SPR-13075.
73+
*/
74+
@Test
75+
public void rootWacServletContainerAttributePreviouslySetWithContextHierarchy() {
76+
StubWebApplicationContext root = new StubWebApplicationContext(this.servletContext);
77+
78+
this.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, root);
79+
80+
StaticWebApplicationContext child = new StaticWebApplicationContext();
81+
child.setParent(root);
82+
child.setServletContext(this.servletContext);
83+
84+
DefaultMockMvcBuilder builder = webAppContextSetup(child);
85+
assertSame(builder.initWebAppContext().getParent(),
86+
WebApplicationContextUtils.getRequiredWebApplicationContext(this.servletContext));
87+
}
88+
89+
/**
90+
* See SPR-12553 and SPR-13075.
91+
*/
92+
@Test
93+
public void rootWacServletContainerAttributeNotPreviouslySet() {
94+
StubWebApplicationContext root = new StubWebApplicationContext(this.servletContext);
95+
DefaultMockMvcBuilder builder = webAppContextSetup(root);
96+
WebApplicationContext wac = builder.initWebAppContext();
97+
assertSame(root, wac);
98+
assertSame(root, WebApplicationContextUtils.getRequiredWebApplicationContext(this.servletContext));
99+
}
100+
101+
/**
102+
* See SPR-12553 and SPR-13075.
103+
*/
104+
@Test
105+
public void rootWacServletContainerAttributeNotPreviouslySetWithContextHierarchy() {
106+
StaticApplicationContext ear = new StaticApplicationContext();
107+
StaticWebApplicationContext root = new StaticWebApplicationContext();
108+
root.setParent(ear);
109+
root.setServletContext(this.servletContext);
110+
StaticWebApplicationContext dispatcher = new StaticWebApplicationContext();
111+
dispatcher.setParent(root);
112+
dispatcher.setServletContext(this.servletContext);
113+
114+
DefaultMockMvcBuilder builder = webAppContextSetup(dispatcher);
115+
WebApplicationContext wac = builder.initWebAppContext();
116+
117+
assertSame(dispatcher, wac);
118+
assertSame(root, wac.getParent());
119+
assertSame(ear, wac.getParent().getParent());
120+
assertSame(root, WebApplicationContextUtils.getRequiredWebApplicationContext(this.servletContext));
37121
}
38122

39123
}

0 commit comments

Comments
 (0)