Support loading WebApplicationContexts in the TCF

Prior to this commit, the Spring TestContext Framework only supported
loading an ApplicationContext in integration tests from either XML or
Java Properties files (since Spring 2.5), and Spring 3.1 introduced
support for loading an ApplicationContext in integration tests from
annotated classes (e.g., @Configuration classes). All of the
ContextLoader implementations used to provide this support load a
GenericApplicationContext. However, a GenericApplicationContext is not
suitable for testing a web application since a web application relies on
an implementation of WebApplicationContext (WAC).

This commit makes it possible to integration test Spring-powered web
applications by adding the following functionality to the Spring
TestContext Framework.

 - Introduced AbstractGenericWebContextLoader and two concrete
   subclasses:
   - XmlWebContextLoader
   - AnnotationConfigWebContextLoader

 - Pulled up prepareContext(context, mergedConfig) from
   AbstractGenericContextLoader into AbstractContextLoader to allow it
   to be shared across web and non-web context loaders.

 - Introduced AnnotationConfigContextLoaderUtils and refactored
   AnnotationConfigContextLoader accordingly. These utils are also used
   by AnnotationConfigWebContextLoader.

 - Introduced a new @WebAppConfiguration annotation to denote that the
   ApplicationContext loaded for a test should be a WAC and to configure
   the base resource path for the root directory of a web application.

 - Introduced WebMergedContextConfiguration which extends
   MergedContextConfiguration with support for a baseResourcePath for
   the root directory of a web application.

 - ContextLoaderUtils.buildMergedContextConfiguration() now builds a
   WebMergedContextConfiguration instead of a standard
   MergedContextConfiguration if @WebAppConfiguration is present on the
   test class.

 - Introduced a configureWebResources() method in
   AbstractGenericWebContextLoader that is responsible for creating a
   MockServletContext with a proper ResourceLoader for the
   resourceBasePath configured in the WebMergedContextConfiguration. The
   resulting mock ServletContext is set in the WAC, and the WAC is
   stored as the Root WAC in the ServletContext.

 - Introduced a WebTestExecutionListener that sets up default thread
   local state via RequestContextHolder before each test method by using
   the MockServletContext already present in the WAC and by creating a
   MockHttpServletRequest, MockHttpServletResponse, and
   ServletWebRequest that is set in the RequestContextHolder. WTEL also
   ensures that the MockHttpServletResponse and ServletWebRequest can be
   injected into the test instance (e.g., via @Autowired) and cleans up
   thread locals after each test method.

 - WebTestExecutionListener is configured as a default
   TestExecutionListener before DependencyInjectionTestExecutionListener

 - Extracted AbstractDelegatingSmartContextLoader from
   DelegatingSmartContextLoader and introduced a new
   WebDelegatingSmartContextLoader.

 - ContextLoaderUtils now selects the default delegating ContextLoader
   class name based on the presence of @WebAppConfiguration on the test
   class.

 - Tests in the spring-test-mvc module no longer use a custom
   ContextLoader to load a WebApplicationContext. Instead, they now
   rely on new core functionality provided in this commit.

Issue: SPR-5243
This commit is contained in:
Sam Brannen
2012-10-06 22:24:55 +02:00
parent 3552173b81
commit a73280ccc8
29 changed files with 1474 additions and 601 deletions

View File

@@ -1,94 +0,0 @@
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.mock.servlet.samples.context;
import javax.servlet.RequestDispatcher;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.mock.web.MockRequestDispatcher;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.support.AbstractContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
/**
* This class is here temporarily until the TestContext framework provides
* support for WebApplicationContext yet:
*
* https://jira.springsource.org/browse/SPR-5243
*
* <p>After that this class will no longer be needed. It's provided here as an example
* and to serve as a temporary solution.
*/
public class GenericWebContextLoader extends AbstractContextLoader {
protected final MockServletContext servletContext;
public GenericWebContextLoader(String warRootDir, boolean isClasspathRelative) {
ResourceLoader resourceLoader = isClasspathRelative ? new DefaultResourceLoader() : new FileSystemResourceLoader();
this.servletContext = initServletContext(warRootDir, resourceLoader);
}
private MockServletContext initServletContext(String warRootDir, ResourceLoader resourceLoader) {
return new MockServletContext(warRootDir, resourceLoader) {
// Required for DefaultServletHttpRequestHandler...
public RequestDispatcher getNamedDispatcher(String path) {
return (path.equals("default")) ? new MockRequestDispatcher(path) : super.getNamedDispatcher(path);
}
};
}
public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
GenericWebApplicationContext context = new GenericWebApplicationContext();
context.getEnvironment().setActiveProfiles(mergedConfig.getActiveProfiles());
prepareContext(context);
loadBeanDefinitions(context, mergedConfig);
return context;
}
public ApplicationContext loadContext(String... locations) throws Exception {
// should never be called
throw new UnsupportedOperationException();
}
protected void prepareContext(GenericWebApplicationContext context) {
this.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
context.setServletContext(this.servletContext);
}
protected void loadBeanDefinitions(GenericWebApplicationContext context, String[] locations) {
new XmlBeanDefinitionReader(context).loadBeanDefinitions(locations);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
context.refresh();
context.registerShutdownHook();
}
protected void loadBeanDefinitions(GenericWebApplicationContext context, MergedContextConfiguration mergedConfig) {
new AnnotatedBeanDefinitionReader(context).register(mergedConfig.getClasses());
loadBeanDefinitions(context, mergedConfig.getLocations());
}
@Override
protected String getResourceSuffix() {
return "-context.xml";
}
}

View File

@@ -16,9 +16,8 @@
package org.springframework.test.web.mock.servlet.samples.context;
import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.*;
import org.junit.Before;
import org.junit.Test;
@@ -26,21 +25,20 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.mock.servlet.MockMvc;
import org.springframework.test.web.mock.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
/**
* Tests with Java configuration.
*
* The TestContext framework doesn't support WebApplicationContext yet:
* https://jira.springsource.org/browse/SPR-5243
*
* A custom {@link ContextLoader} is used to load the WebApplicationContext.
* @author Rossen Stoyanchev
* @author Sam Brannen
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=WebContextLoader.class, classes={WebConfig.class})
@WebAppConfiguration("src/test/resources/META-INF/web-resources")
@ContextConfiguration(classes = WebConfig.class)
public class JavaTestContextTests {
@Autowired
@@ -48,6 +46,7 @@ public class JavaTestContextTests {
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
@@ -55,9 +54,9 @@ public class JavaTestContextTests {
@Test
public void tilesDefinitions() throws Exception {
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp"));
this.mockMvc.perform(get("/"))//
.andExpect(status().isOk())//
.andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp"));
}
}

View File

@@ -10,6 +10,7 @@
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.springframework.test.web.mock.servlet.samples.context;
import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get;
@@ -34,6 +35,7 @@ import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.mock.servlet.MockMvc;
import org.springframework.test.web.mock.servlet.MvcResult;
import org.springframework.test.web.mock.servlet.ResultMatcher;
@@ -44,32 +46,25 @@ import org.springframework.web.context.WebApplicationContext;
/**
* Basic example that includes Spring Security configuration.
*
* <p>Note that currently there are no {@link ResultMatcher}' built specifically
* for asserting the Spring Security context. However, it's quite easy to put
* them together as shown below and Spring Security extensions will become
* available in the near future.
* <p>Note that currently there are no {@linkplain ResultMatcher ResultMatchers}
* built specifically for asserting the Spring Security context. However, it's
* quite easy to put them together as shown below, and Spring Security extensions
* will become available in the near future.
*
* <p>This also demonstrates a custom {@link RequestPostProcessor} which authenticates
* a user to a particular {@link HttpServletRequest}.
*
* <p>Also see the Javadoc of {@link GenericWebContextLoader}, a class that
* provides temporary support for loading WebApplicationContext by extending
* the TestContext framework.
*
* @author Rob Winch
* @author Rossen Stoyanchev
* @author Sam Brannen
* @see SecurityRequestPostProcessors
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=WebContextLoader.class,
value={
"classpath:org/springframework/test/web/mock/servlet/samples/context/security.xml",
"classpath:org/springframework/test/web/mock/servlet/samples/servlet-context.xml"
})
@WebAppConfiguration("src/test/resources/META-INF/web-resources")
@ContextConfiguration({ "security.xml", "../servlet-context.xml" })
public class SpringSecurityTests {
private static String SEC_CONTEXT_ATTR = HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
private static final String SEC_CONTEXT_ATTR = HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
@Autowired
private FilterChainProxy springSecurityFilterChain;
@@ -79,57 +74,67 @@ public class SpringSecurityTests {
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.addFilters(this.springSecurityFilterChain).build();
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)//
.addFilters(this.springSecurityFilterChain)//
.build();
}
@Test
public void requiresAuthentication() throws Exception {
mockMvc.perform(get("/user"))
.andExpect(redirectedUrl("http://localhost/spring_security_login"));
mockMvc.perform(get("/user")).//
andExpect(redirectedUrl("http://localhost/spring_security_login"));
}
@Test
public void accessGranted() throws Exception {
this.mockMvc.perform(get("/").with(userDeatilsService("user")))
.andExpect(status().isOk())
.andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp"));
this.mockMvc.perform(get("/").//
with(userDeatilsService("user"))).//
andExpect(status().isOk()).//
andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp"));
}
@Test
public void accessDenied() throws Exception {
this.mockMvc.perform(get("/").with(user("user").roles("DENIED")))
.andExpect(status().isForbidden());
this.mockMvc.perform(get("/")//
.with(user("user").roles("DENIED")))//
.andExpect(status().isForbidden());
}
@Test
public void userAuthenticates() throws Exception {
final String username = "user";
mockMvc.perform(post("/j_spring_security_check").param("j_username", username).param("j_password", "password"))
.andExpect(redirectedUrl("/"))
.andExpect(new ResultMatcher() {
public void match(MvcResult mvcResult) throws Exception {
HttpSession session = mvcResult.getRequest().getSession();
SecurityContext securityContext = (SecurityContext) session.getAttribute(SEC_CONTEXT_ATTR);
Assert.assertEquals(securityContext.getAuthentication().getName(), username);
}
});
mockMvc.perform(post("/j_spring_security_check").//
param("j_username", username).//
param("j_password", "password")).//
andExpect(redirectedUrl("/")).//
andExpect(new ResultMatcher() {
public void match(MvcResult mvcResult) throws Exception {
HttpSession session = mvcResult.getRequest().getSession();
SecurityContext securityContext = (SecurityContext) session.getAttribute(SEC_CONTEXT_ATTR);
Assert.assertEquals(securityContext.getAuthentication().getName(), username);
}
});
}
@Test
public void userAuthenticateFails() throws Exception {
final String username = "user";
mockMvc.perform(post("/j_spring_security_check").param("j_username", username).param("j_password", "invalid"))
.andExpect(redirectedUrl("/spring_security_login?login_error"))
.andExpect(new ResultMatcher() {
public void match(MvcResult mvcResult) throws Exception {
HttpSession session = mvcResult.getRequest().getSession();
SecurityContext securityContext = (SecurityContext) session.getAttribute(SEC_CONTEXT_ATTR);
Assert.assertNull(securityContext);
}
});
mockMvc.perform(post("/j_spring_security_check").//
param("j_username", username).//
param("j_password", "invalid")).//
andExpect(redirectedUrl("/spring_security_login?login_error")).//
andExpect(new ResultMatcher() {
public void match(MvcResult mvcResult) throws Exception {
HttpSession session = mvcResult.getRequest().getSession();
SecurityContext securityContext = (SecurityContext) session.getAttribute(SEC_CONTEXT_ATTR);
Assert.assertNull(securityContext);
}
});
}
}

View File

@@ -1,24 +0,0 @@
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.mock.servlet.samples.context;
class WebContextLoader extends GenericWebContextLoader {
public WebContextLoader() {
super("src/test/resources/META-INF/web-resources", false);
}
}

View File

@@ -16,9 +16,8 @@
package org.springframework.test.web.mock.servlet.samples.context;
import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.*;
import org.junit.Before;
import org.junit.Test;
@@ -26,23 +25,20 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.mock.servlet.MockMvc;
import org.springframework.test.web.mock.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
/**
* Tests with XML configuration.
*
* The TestContext framework doesn't support WebApplicationContext yet:
* https://jira.springsource.org/browse/SPR-5243
*
* A custom {@link ContextLoader} is used to load the WebApplicationContext.
* @author Rossen Stoyanchev
* @author Sam Brannen
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=WebContextLoader.class,
locations={"/org/springframework/test/web/mock/servlet/samples/servlet-context.xml"})
@WebAppConfiguration("src/test/resources/META-INF/web-resources")
@ContextConfiguration("../servlet-context.xml")
public class XmlTestContextTests {
@Autowired
@@ -50,6 +46,7 @@ public class XmlTestContextTests {
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
@@ -57,10 +54,9 @@ public class XmlTestContextTests {
@Test
public void tilesDefinitions() throws Exception {
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp"));
this.mockMvc.perform(get("/"))//
.andExpect(status().isOk())//
.andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp"));
}
}