diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/GenericWebContextLoader.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/GenericWebContextLoader.java deleted file mode 100644 index 55c477ccc342..000000000000 --- a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/GenericWebContextLoader.java +++ /dev/null @@ -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 - * - *

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"; - } -} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/JavaTestContextTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/JavaTestContextTests.java index 722acba41e71..dbb5ff73aebd 100644 --- a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/JavaTestContextTests.java +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/JavaTestContextTests.java @@ -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.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 void setup() { @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")); } } diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/SpringSecurityTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/SpringSecurityTests.java index e24bdba57da6..b1da11b99ffd 100644 --- a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/SpringSecurityTests.java +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/SpringSecurityTests.java @@ -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.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 @@ /** * Basic example that includes Spring Security configuration. * - *

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. + *

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. * *

This also demonstrates a custom {@link RequestPostProcessor} which authenticates * a user to a particular {@link HttpServletRequest}. * - *

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); + } + }); } } diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/XmlTestContextTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/XmlTestContextTests.java index 2edc313d033d..3274c6137d4d 100644 --- a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/XmlTestContextTests.java +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/XmlTestContextTests.java @@ -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.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 void setup() { @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")); } } - diff --git a/spring-test/.springBeans b/spring-test/.springBeans index 7d64f21cfb8a..a4a7e241718f 100644 --- a/spring-test/.springBeans +++ b/spring-test/.springBeans @@ -9,6 +9,7 @@ src/test/java/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests-context.xml src/test/java/org/springframework/test/context/junit4/aci/xml/MultipleInitializersXmlConfigTests-context.xml + src/test/resources/org/springframework/test/context/web/BasicXmlWacTests-config.xml diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java index a5ad3516f056..781f7bd7a2c1 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java @@ -16,8 +16,8 @@ package org.springframework.test.context; -import static org.springframework.beans.BeanUtils.instantiateClass; -import static org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClass; +import static org.springframework.beans.BeanUtils.*; +import static org.springframework.core.annotation.AnnotationUtils.*; import java.util.ArrayList; import java.util.Arrays; @@ -30,6 +30,8 @@ import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.context.web.WebMergedContextConfiguration; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -54,6 +56,7 @@ abstract class ContextLoaderUtils { private static final Log logger = LogFactory.getLog(ContextLoaderUtils.class); private static final String DEFAULT_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.support.DelegatingSmartContextLoader"; + private static final String DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.support.WebDelegatingSmartContextLoader"; private ContextLoaderUtils() { @@ -83,7 +86,8 @@ static ContextLoader resolveContextLoader(Class testClass, String defaultCont Assert.notNull(testClass, "Test class must not be null"); if (!StringUtils.hasText(defaultContextLoaderClassName)) { - defaultContextLoaderClassName = DEFAULT_CONTEXT_LOADER_CLASS_NAME; + defaultContextLoaderClassName = testClass.isAnnotationPresent(WebAppConfiguration.class) ? DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME + : DEFAULT_CONTEXT_LOADER_CLASS_NAME; } Class contextLoaderClass = resolveContextLoaderClass(testClass, @@ -394,6 +398,14 @@ static MergedContextConfiguration buildMergedContextConfiguration(Class testC Set>> initializerClasses = resolveInitializerClasses(configAttributesList); String[] activeProfiles = resolveActiveProfiles(testClass); + if (testClass.isAnnotationPresent(WebAppConfiguration.class)) { + WebAppConfiguration webAppConfig = testClass.getAnnotation(WebAppConfiguration.class); + String resourceBasePath = webAppConfig.value(); + return new WebMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles, + resourceBasePath, contextLoader); + } + + // else return new MergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles, contextLoader); } diff --git a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java index e484253b8c37..6fc402e92ffb 100644 --- a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java @@ -105,7 +105,7 @@ private static String[] processActiveProfiles(String[] activeProfiles) { * {@link ContextLoader} based solely on the fully qualified name of the * loader or "null" if the supplied loaded is null. */ - private static String nullSafeToString(ContextLoader contextLoader) { + protected static String nullSafeToString(ContextLoader contextLoader) { return contextLoader == null ? "null" : contextLoader.getClass().getName(); } diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContext.java b/spring-test/src/main/java/org/springframework/test/context/TestContext.java index 7b7426b1049f..6d3a90c663d0 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestContext.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestContext.java @@ -114,7 +114,7 @@ public class TestContext extends AttributeAccessorSupport { */ private ApplicationContext loadApplicationContext() throws Exception { ContextLoader contextLoader = mergedContextConfiguration.getContextLoader(); - Assert.notNull(contextLoader, "Can not load an ApplicationContext with a NULL 'contextLoader'. " + Assert.notNull(contextLoader, "Cannot load an ApplicationContext with a NULL 'contextLoader'. " + "Consider annotating your test class with @ContextConfiguration."); ApplicationContext applicationContext; @@ -125,7 +125,7 @@ private ApplicationContext loadApplicationContext() throws Exception { } else { String[] locations = mergedContextConfiguration.getLocations(); - Assert.notNull(locations, "Can not load an ApplicationContext with a NULL 'locations' array. " + Assert.notNull(locations, "Cannot load an ApplicationContext with a NULL 'locations' array. " + "Consider annotating your test class with @ContextConfiguration."); applicationContext = contextLoader.loadContext(locations); } diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java b/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java index b5ba968ba0ca..e3e42b6706c9 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java @@ -76,6 +76,7 @@ public class TestContextManager { private static final String[] DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES = new String[] { + "org.springframework.test.context.web.WebTestExecutionListener", "org.springframework.test.context.support.DependencyInjectionTestExecutionListener", "org.springframework.test.context.support.DirtiesContextTestExecutionListener", "org.springframework.test.context.transaction.TransactionalTestExecutionListener" }; @@ -126,7 +127,6 @@ protected final TestContext getTestContext() { return this.testContext; } - /** * Register the supplied {@link TestExecutionListener TestExecutionListeners} * by appending them to the set of listeners used by this TestContextManager. @@ -155,8 +155,8 @@ public final List getTestExecutionListeners() { * registered for this TestContextManager in reverse order. */ private List getReversedTestExecutionListeners() { - List listenersReversed = - new ArrayList(getTestExecutionListeners()); + List listenersReversed = new ArrayList( + getTestExecutionListeners()); Collections.reverse(listenersReversed); return listenersReversed; } @@ -186,8 +186,7 @@ private TestExecutionListener[] retrieveTestExecutionListeners(Class clazz) { } classesList.addAll(getDefaultTestExecutionListenerClasses()); defaultListeners = true; - } - else { + } else { // Traverse the class hierarchy... while (declaringClass != null) { TestExecutionListeners testExecutionListeners = declaringClass.getAnnotation(annotationType); @@ -200,22 +199,21 @@ private TestExecutionListener[] retrieveTestExecutionListeners(Class clazz) { Class[] listenerClasses = testExecutionListeners.listeners(); if (!ObjectUtils.isEmpty(valueListenerClasses) && !ObjectUtils.isEmpty(listenerClasses)) { String msg = String.format( - "Test class [%s] has been configured with @TestExecutionListeners' 'value' [%s] " + - "and 'listeners' [%s] attributes. Use one or the other, but not both.", + "Test class [%s] has been configured with @TestExecutionListeners' 'value' [%s] " + + "and 'listeners' [%s] attributes. Use one or the other, but not both.", declaringClass, ObjectUtils.nullSafeToString(valueListenerClasses), ObjectUtils.nullSafeToString(listenerClasses)); logger.error(msg); throw new IllegalStateException(msg); - } - else if (!ObjectUtils.isEmpty(valueListenerClasses)) { + } else if (!ObjectUtils.isEmpty(valueListenerClasses)) { listenerClasses = valueListenerClasses; } if (listenerClasses != null) { classesList.addAll(0, Arrays.> asList(listenerClasses)); } - declaringClass = (testExecutionListeners.inheritListeners() ? - AnnotationUtils.findAnnotationDeclaringClass(annotationType, declaringClass.getSuperclass()) : null); + declaringClass = (testExecutionListeners.inheritListeners() ? AnnotationUtils.findAnnotationDeclaringClass( + annotationType, declaringClass.getSuperclass()) : null); } } @@ -223,16 +221,14 @@ else if (!ObjectUtils.isEmpty(valueListenerClasses)) { for (Class listenerClass : classesList) { try { listeners.add(BeanUtils.instantiateClass(listenerClass)); - } - catch (NoClassDefFoundError err) { + } catch (NoClassDefFoundError err) { if (defaultListeners) { if (logger.isDebugEnabled()) { logger.debug("Could not instantiate default TestExecutionListener class [" + listenerClass.getName() + "]. Specify custom listener classes or make the default listener classes available."); } - } - else { + } else { throw err; } } @@ -245,24 +241,21 @@ else if (!ObjectUtils.isEmpty(valueListenerClasses)) { */ @SuppressWarnings("unchecked") protected Set> getDefaultTestExecutionListenerClasses() { - Set> defaultListenerClasses = - new LinkedHashSet>(); + Set> defaultListenerClasses = new LinkedHashSet>(); for (String className : DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES) { try { - defaultListenerClasses.add( - (Class) getClass().getClassLoader().loadClass(className)); - } - catch (Throwable ex) { + defaultListenerClasses.add((Class) getClass().getClassLoader().loadClass( + className)); + } catch (Throwable t) { if (logger.isDebugEnabled()) { logger.debug("Could not load default TestExecutionListener class [" + className - + "]. Specify custom listener classes or make the default listener classes available."); + + "]. Specify custom listener classes or make the default listener classes available.", t); } } } return defaultListenerClasses; } - /** * Hook for pre-processing a test class before execution of any * tests within the class. Should be called prior to any framework-specific @@ -286,8 +279,7 @@ public void beforeTestClass() throws Exception { for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) { try { testExecutionListener.beforeTestClass(getTestContext()); - } - catch (Exception ex) { + } catch (Exception ex) { logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + "] to process 'before class' callback for test class [" + testClass + "]", ex); throw ex; @@ -319,8 +311,7 @@ public void prepareTestInstance(Object testInstance) throws Exception { for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) { try { testExecutionListener.prepareTestInstance(getTestContext()); - } - catch (Exception ex) { + } catch (Exception ex) { logger.error("Caught exception while allowing TestExecutionListener [" + testExecutionListener + "] to prepare test instance [" + testInstance + "]", ex); throw ex; @@ -356,8 +347,7 @@ public void beforeTestMethod(Object testInstance, Method testMethod) throws Exce for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) { try { testExecutionListener.beforeTestMethod(getTestContext()); - } - catch (Exception ex) { + } catch (Exception ex) { logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + "] to process 'before' execution of test method [" + testMethod + "] for test instance [" + testInstance + "]", ex); @@ -404,8 +394,7 @@ public void afterTestMethod(Object testInstance, Method testMethod, Throwable ex for (TestExecutionListener testExecutionListener : getReversedTestExecutionListeners()) { try { testExecutionListener.afterTestMethod(getTestContext()); - } - catch (Exception ex) { + } catch (Exception ex) { logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + "] to process 'after' execution for test: method [" + testMethod + "], instance [" + testInstance + "], exception [" + exception + "]", ex); @@ -446,8 +435,7 @@ public void afterTestClass() throws Exception { for (TestExecutionListener testExecutionListener : getReversedTestExecutionListeners()) { try { testExecutionListener.afterTestClass(getTestContext()); - } - catch (Exception ex) { + } catch (Exception ex) { logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + "] to process 'after class' callback for test class [" + testClass + "]", ex); if (afterTestClassException == null) { diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java index 0fe803569501..6f114738fff8 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java @@ -16,13 +16,24 @@ package org.springframework.test.context.support; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeanUtils; import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.GenericTypeResolver; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextLoader; +import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.SmartContextLoader; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -81,6 +92,64 @@ public void processContextConfiguration(ContextConfigurationAttributes configAtt configAttributes.setLocations(processedLocations); } + /** + * Prepare the {@link ConfigurableApplicationContext} created by this + * {@code SmartContextLoader} before bean definitions are read. + * + *

The default implementation: + *

+ * + *

Any {@code ApplicationContextInitializers} implementing + * {@link org.springframework.core.Ordered Ordered} or marked with {@link + * org.springframework.core.annotation.Order @Order} will be sorted appropriately. + * + * @param context the newly created application context + * @param mergedConfig the merged context configuration + * @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext) + * @see #loadContext(MergedContextConfiguration) + * @see ConfigurableApplicationContext#setId + * @since 3.2 + */ + @SuppressWarnings("unchecked") + protected void prepareContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { + + context.getEnvironment().setActiveProfiles(mergedConfig.getActiveProfiles()); + + Set>> initializerClasses = mergedConfig.getContextInitializerClasses(); + + if (initializerClasses.size() == 0) { + // no ApplicationContextInitializers have been declared -> nothing to do + return; + } + + final List> initializerInstances = new ArrayList>(); + final Class contextClass = context.getClass(); + + for (Class> initializerClass : initializerClasses) { + Class initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass, + ApplicationContextInitializer.class); + Assert.isAssignable(initializerContextClass, contextClass, String.format( + "Could not add context initializer [%s] since its generic parameter [%s] " + + "is not assignable from the type of application context used by this " + + "context loader [%s]: ", initializerClass.getName(), initializerContextClass.getName(), + contextClass.getName())); + initializerInstances.add((ApplicationContextInitializer) BeanUtils.instantiateClass(initializerClass)); + } + + Collections.sort(initializerInstances, new AnnotationAwareOrderComparator()); + for (ApplicationContextInitializer initializer : initializerInstances) { + initializer.initialize(context); + } + } + // --- ContextLoader ------------------------------------------------------- /** @@ -185,12 +254,10 @@ protected String[] modifyLocations(Class clazz, String... locations) { String path = locations[i]; if (path.startsWith(SLASH)) { modifiedLocations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + path; - } - else if (!ResourcePatternUtils.isUrl(path)) { + } else if (!ResourcePatternUtils.isUrl(path)) { modifiedLocations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + SLASH + StringUtils.cleanPath(ClassUtils.classPackageAsResourcePath(clazz) + SLASH + path); - } - else { + } else { modifiedLocations[i] = StringUtils.cleanPath(path); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java new file mode 100644 index 000000000000..5a05a24c4ed1 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java @@ -0,0 +1,287 @@ +/* + * 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.context.support; + +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextLoader; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.test.context.SmartContextLoader; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * {@code AbstractDelegatingSmartContextLoader} serves as an abstract base class + * for implementations of the {@link SmartContextLoader} SPI that delegate to a + * set of candidate SmartContextLoaders (i.e., one that supports XML + * configuration files and one that supports annotated classes) to determine which + * context loader is appropriate for a given test class's configuration. Each + * candidate is given a chance to {@link #processContextConfiguration process} the + * {@link ContextConfigurationAttributes} for each class in the test class hierarchy + * that is annotated with {@link ContextConfiguration @ContextConfiguration}, and + * the candidate that supports the merged, processed configuration will be used to + * actually {@link #loadContext load} the context. + * + *

Placing an empty {@code @ContextConfiguration} annotation on a test class signals + * that default resource locations (i.e., XML configuration files) or default + * {@link org.springframework.context.annotation.Configuration configuration classes} + * should be detected. Furthermore, if a specific {@link ContextLoader} or + * {@link SmartContextLoader} is not explicitly declared via + * {@code @ContextConfiguration}, a concrete subclass of + * {@code AbstractDelegatingSmartContextLoader} will be used as the default loader, + * thus providing automatic support for either XML configuration files or annotated + * classes, but not both simultaneously. + * + *

As of Spring 3.2, a test class may optionally declare neither XML configuration + * files nor annotated classes and instead declare only {@linkplain + * ContextConfiguration#initializers application context initializers}. In such + * cases, an attempt will still be made to detect defaults, but their absence will + * not result an an exception. + * + * @author Sam Brannen + * @since 3.2 + * @see SmartContextLoader + */ +abstract class AbstractDelegatingSmartContextLoader implements SmartContextLoader { + + private static final Log logger = LogFactory.getLog(AbstractDelegatingSmartContextLoader.class); + + + /** + * Get the delegate {@code SmartContextLoader} that supports XML configuration files. + */ + protected abstract SmartContextLoader getXmlLoader(); + + /** + * Get the delegate {@code SmartContextLoader} that supports annotated classes. + */ + protected abstract SmartContextLoader getAnnotationConfigLoader(); + + // --- SmartContextLoader -------------------------------------------------- + + private static String name(SmartContextLoader loader) { + return loader.getClass().getSimpleName(); + } + + private static void delegateProcessing(SmartContextLoader loader, ContextConfigurationAttributes configAttributes) { + if (logger.isDebugEnabled()) { + logger.debug(String.format("Delegating to %s to process context configuration %s.", name(loader), + configAttributes)); + } + loader.processContextConfiguration(configAttributes); + } + + private static ApplicationContext delegateLoading(SmartContextLoader loader, MergedContextConfiguration mergedConfig) + throws Exception { + if (logger.isDebugEnabled()) { + logger.debug(String.format("Delegating to %s to load context from %s.", name(loader), mergedConfig)); + } + return loader.loadContext(mergedConfig); + } + + private boolean supports(SmartContextLoader loader, MergedContextConfiguration mergedConfig) { + if (loader == getAnnotationConfigLoader()) { + return ObjectUtils.isEmpty(mergedConfig.getLocations()) && !ObjectUtils.isEmpty(mergedConfig.getClasses()); + } else { + return !ObjectUtils.isEmpty(mergedConfig.getLocations()) && ObjectUtils.isEmpty(mergedConfig.getClasses()); + } + } + + /** + * Delegates to candidate {@code SmartContextLoaders} to process the supplied + * {@link ContextConfigurationAttributes}. + * + *

Delegation is based on explicit knowledge of the implementations of the + * default loaders for {@link #getXmlLoader() XML configuration files} and + * {@link #getAnnotationConfigLoader() annotated classes}. Specifically, the + * delegation algorithm is as follows: + * + *

+ * + * @param configAttributes the context configuration attributes to process + * @throws IllegalArgumentException if the supplied configuration attributes are + * null, or if the supplied configuration attributes include both + * resource locations and annotated classes + * @throws IllegalStateException if the XML-based loader detects default + * configuration classes; if the annotation-based loader detects default + * resource locations; if neither candidate loader detects defaults for the supplied + * context configuration; or if both candidate loaders detect defaults for the + * supplied context configuration + */ + public void processContextConfiguration(final ContextConfigurationAttributes configAttributes) { + + Assert.notNull(configAttributes, "configAttributes must not be null"); + Assert.isTrue(!(configAttributes.hasLocations() && configAttributes.hasClasses()), String.format( + "Cannot process locations AND classes for context " + + "configuration %s; configure one or the other, but not both.", configAttributes)); + + // If the original locations or classes were not empty, there's no + // need to bother with default detection checks; just let the + // appropriate loader process the configuration. + if (configAttributes.hasLocations()) { + delegateProcessing(getXmlLoader(), configAttributes); + } else if (configAttributes.hasClasses()) { + delegateProcessing(getAnnotationConfigLoader(), configAttributes); + } else { + // Else attempt to detect defaults... + + // Let the XML loader process the configuration. + delegateProcessing(getXmlLoader(), configAttributes); + boolean xmlLoaderDetectedDefaults = configAttributes.hasLocations(); + + if (xmlLoaderDetectedDefaults) { + if (logger.isInfoEnabled()) { + logger.info(String.format("%s detected default locations for context configuration %s.", + name(getXmlLoader()), configAttributes)); + } + } + + if (configAttributes.hasClasses()) { + throw new IllegalStateException(String.format( + "%s should NOT have detected default configuration classes for context configuration %s.", + name(getXmlLoader()), configAttributes)); + } + + // Now let the annotation config loader process the configuration. + delegateProcessing(getAnnotationConfigLoader(), configAttributes); + + if (configAttributes.hasClasses()) { + if (logger.isInfoEnabled()) { + logger.info(String.format( + "%s detected default configuration classes for context configuration %s.", + name(getAnnotationConfigLoader()), configAttributes)); + } + } + + if (!xmlLoaderDetectedDefaults && configAttributes.hasLocations()) { + throw new IllegalStateException(String.format( + "%s should NOT have detected default locations for context configuration %s.", + name(getAnnotationConfigLoader()), configAttributes)); + } + + // If neither loader detected defaults and no initializers were declared, + // throw an exception. + if (!configAttributes.hasResources() && ObjectUtils.isEmpty(configAttributes.getInitializers())) { + throw new IllegalStateException(String.format( + "Neither %s nor %s was able to detect defaults, and no ApplicationContextInitializers " + + "were declared for context configuration %s", name(getXmlLoader()), + name(getAnnotationConfigLoader()), configAttributes)); + } + + if (configAttributes.hasLocations() && configAttributes.hasClasses()) { + String message = String.format( + "Configuration error: both default locations AND default configuration classes " + + "were detected for context configuration %s; configure one or the other, but not both.", + configAttributes); + logger.error(message); + throw new IllegalStateException(message); + } + } + } + + /** + * Delegates to an appropriate candidate {@code SmartContextLoader} to load + * an {@link ApplicationContext}. + * + *

Delegation is based on explicit knowledge of the implementations of the + * default loaders for {@link #getXmlLoader() XML configuration files} and + * {@link #getAnnotationConfigLoader() annotated classes}. Specifically, the + * delegation algorithm is as follows: + * + *

+ * + * @param mergedConfig the merged context configuration to use to load the application context + * @throws IllegalArgumentException if the supplied merged configuration is null + * @throws IllegalStateException if neither candidate loader is capable of loading an + * {@code ApplicationContext} from the supplied merged context configuration + */ + public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception { + Assert.notNull(mergedConfig, "mergedConfig must not be null"); + + List candidates = Arrays.asList(getXmlLoader(), getAnnotationConfigLoader()); + + for (SmartContextLoader loader : candidates) { + // Determine if each loader can load a context from the mergedConfig. If it + // can, let it; otherwise, keep iterating. + if (supports(loader, mergedConfig)) { + return delegateLoading(loader, mergedConfig); + } + } + + // If neither of the candidates supports the mergedConfig based on resources but + // ACIs were declared, then delegate to the annotation config loader. + if (!mergedConfig.getContextInitializerClasses().isEmpty()) { + return delegateLoading(getAnnotationConfigLoader(), mergedConfig); + } + + throw new IllegalStateException(String.format( + "Neither %s nor %s was able to load an ApplicationContext from %s.", name(getXmlLoader()), + name(getAnnotationConfigLoader()), mergedConfig)); + } + + // --- ContextLoader ------------------------------------------------------- + + /** + * {@code AbstractDelegatingSmartContextLoader} does not support the + * {@link ContextLoader#processLocations(Class, String...)} method. Call + * {@link #processContextConfiguration(ContextConfigurationAttributes)} instead. + * @throws UnsupportedOperationException + */ + public final String[] processLocations(Class clazz, String... locations) { + throw new UnsupportedOperationException("DelegatingSmartContextLoaders do not support the ContextLoader SPI. " + + "Call processContextConfiguration(ContextConfigurationAttributes) instead."); + } + + /** + * {@code AbstractDelegatingSmartContextLoader} does not support the + * {@link ContextLoader#loadContext(String...) } method. Call + * {@link #loadContext(MergedContextConfiguration)} instead. + * @throws UnsupportedOperationException + */ + public final ApplicationContext loadContext(String... locations) throws Exception { + throw new UnsupportedOperationException("DelegatingSmartContextLoaders do not support the ContextLoader SPI. " + + "Call loadContext(MergedContextConfiguration) instead."); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java index 06145c6e94b0..9dba80f96901 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java @@ -16,25 +16,14 @@ package org.springframework.test.context.support; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.support.GenericApplicationContext; -import org.springframework.core.GenericTypeResolver; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.test.context.MergedContextConfiguration; -import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -77,7 +66,10 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader * *