001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.test.context.web; 018 019import javax.servlet.ServletContext; 020 021import org.apache.commons.logging.Log; 022import org.apache.commons.logging.LogFactory; 023 024import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 025import org.springframework.context.ApplicationContext; 026import org.springframework.context.ConfigurableApplicationContext; 027import org.springframework.core.Conventions; 028import org.springframework.core.annotation.AnnotatedElementUtils; 029import org.springframework.mock.web.MockHttpServletRequest; 030import org.springframework.mock.web.MockHttpServletResponse; 031import org.springframework.mock.web.MockServletContext; 032import org.springframework.test.context.TestContext; 033import org.springframework.test.context.TestExecutionListener; 034import org.springframework.test.context.support.AbstractTestExecutionListener; 035import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; 036import org.springframework.web.context.WebApplicationContext; 037import org.springframework.web.context.request.RequestAttributes; 038import org.springframework.web.context.request.RequestContextHolder; 039import org.springframework.web.context.request.ServletWebRequest; 040 041/** 042 * {@code TestExecutionListener} which provides mock Servlet API support to 043 * {@link WebApplicationContext WebApplicationContexts} loaded by the <em>Spring 044 * TestContext Framework</em>. 045 * 046 * <p>Specifically, {@code ServletTestExecutionListener} sets up thread-local 047 * state via Spring Web's {@link RequestContextHolder} during {@linkplain 048 * #prepareTestInstance(TestContext) test instance preparation} and {@linkplain 049 * #beforeTestMethod(TestContext) before each test method} and creates a {@link 050 * MockHttpServletRequest}, {@link MockHttpServletResponse}, and 051 * {@link ServletWebRequest} based on the {@link MockServletContext} present in 052 * the {@code WebApplicationContext}. This listener also ensures that the 053 * {@code MockHttpServletResponse} and {@code ServletWebRequest} can be injected 054 * into the test instance, and once the test is complete this listener {@linkplain 055 * #afterTestMethod(TestContext) cleans up} thread-local state. 056 * 057 * <p>Note that {@code ServletTestExecutionListener} is enabled by default but 058 * generally takes no action if the {@linkplain TestContext#getTestClass() test 059 * class} is not annotated with {@link WebAppConfiguration @WebAppConfiguration}. 060 * See the javadocs for individual methods in this class for details. 061 * 062 * @author Sam Brannen 063 * @author Phillip Webb 064 * @since 3.2 065 */ 066public class ServletTestExecutionListener extends AbstractTestExecutionListener { 067 068 /** 069 * Attribute name for a {@link TestContext} attribute which indicates 070 * whether or not the {@code ServletTestExecutionListener} should {@linkplain 071 * RequestContextHolder#resetRequestAttributes() reset} Spring Web's 072 * {@code RequestContextHolder} in {@link #afterTestMethod(TestContext)}. 073 * <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}. 074 */ 075 public static final String RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE = Conventions.getQualifiedAttributeName( 076 ServletTestExecutionListener.class, "resetRequestContextHolder"); 077 078 /** 079 * Attribute name for a {@link TestContext} attribute which indicates that 080 * {@code ServletTestExecutionListener} has already populated Spring Web's 081 * {@code RequestContextHolder}. 082 * <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}. 083 */ 084 public static final String POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE = Conventions.getQualifiedAttributeName( 085 ServletTestExecutionListener.class, "populatedRequestContextHolder"); 086 087 /** 088 * Attribute name for a request attribute which indicates that the 089 * {@link MockHttpServletRequest} stored in the {@link RequestAttributes} 090 * in Spring Web's {@link RequestContextHolder} was created by the TestContext 091 * framework. 092 * <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}. 093 * @since 4.2 094 */ 095 public static final String CREATED_BY_THE_TESTCONTEXT_FRAMEWORK = Conventions.getQualifiedAttributeName( 096 ServletTestExecutionListener.class, "createdByTheTestContextFramework"); 097 098 /** 099 * Attribute name for a {@link TestContext} attribute which indicates that the 100 * {@code ServletTestExecutionListener} should be activated. When not set to 101 * {@code true}, activation occurs when the {@linkplain TestContext#getTestClass() 102 * test class} is annotated with {@link WebAppConfiguration @WebAppConfiguration}. 103 * <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}. 104 * @since 4.3 105 */ 106 public static final String ACTIVATE_LISTENER = Conventions.getQualifiedAttributeName( 107 ServletTestExecutionListener.class, "activateListener"); 108 109 110 private static final Log logger = LogFactory.getLog(ServletTestExecutionListener.class); 111 112 113 /** 114 * Returns {@code 1000}. 115 */ 116 @Override 117 public final int getOrder() { 118 return 1000; 119 } 120 121 /** 122 * Sets up thread-local state during the <em>test instance preparation</em> 123 * callback phase via Spring Web's {@link RequestContextHolder}, but only if 124 * the {@linkplain TestContext#getTestClass() test class} is annotated with 125 * {@link WebAppConfiguration @WebAppConfiguration}. 126 * @see TestExecutionListener#prepareTestInstance(TestContext) 127 * @see #setUpRequestContextIfNecessary(TestContext) 128 */ 129 @Override 130 public void prepareTestInstance(TestContext testContext) throws Exception { 131 setUpRequestContextIfNecessary(testContext); 132 } 133 134 /** 135 * Sets up thread-local state before each test method via Spring Web's 136 * {@link RequestContextHolder}, but only if the 137 * {@linkplain TestContext#getTestClass() test class} is annotated with 138 * {@link WebAppConfiguration @WebAppConfiguration}. 139 * @see TestExecutionListener#beforeTestMethod(TestContext) 140 * @see #setUpRequestContextIfNecessary(TestContext) 141 */ 142 @Override 143 public void beforeTestMethod(TestContext testContext) throws Exception { 144 setUpRequestContextIfNecessary(testContext); 145 } 146 147 /** 148 * If the {@link #RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE} in the supplied 149 * {@code TestContext} has a value of {@link Boolean#TRUE}, this method will 150 * (1) clean up thread-local state after each test method by {@linkplain 151 * RequestContextHolder#resetRequestAttributes() resetting} Spring Web's 152 * {@code RequestContextHolder} and (2) ensure that new mocks are injected 153 * into the test instance for subsequent tests by setting the 154 * {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE} 155 * in the test context to {@code true}. 156 * <p>The {@link #RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE} and 157 * {@link #POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE} will be subsequently 158 * removed from the test context, regardless of their values. 159 * @see TestExecutionListener#afterTestMethod(TestContext) 160 */ 161 @Override 162 public void afterTestMethod(TestContext testContext) throws Exception { 163 if (Boolean.TRUE.equals(testContext.getAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE))) { 164 if (logger.isDebugEnabled()) { 165 logger.debug(String.format("Resetting RequestContextHolder for test context %s.", testContext)); 166 } 167 RequestContextHolder.resetRequestAttributes(); 168 testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE, 169 Boolean.TRUE); 170 } 171 testContext.removeAttribute(POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE); 172 testContext.removeAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE); 173 } 174 175 private boolean isActivated(TestContext testContext) { 176 return (Boolean.TRUE.equals(testContext.getAttribute(ACTIVATE_LISTENER)) || 177 AnnotatedElementUtils.hasAnnotation(testContext.getTestClass(), WebAppConfiguration.class)); 178 } 179 180 private boolean alreadyPopulatedRequestContextHolder(TestContext testContext) { 181 return Boolean.TRUE.equals(testContext.getAttribute(POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE)); 182 } 183 184 private void setUpRequestContextIfNecessary(TestContext testContext) { 185 if (!isActivated(testContext) || alreadyPopulatedRequestContextHolder(testContext)) { 186 return; 187 } 188 189 ApplicationContext context = testContext.getApplicationContext(); 190 191 if (context instanceof WebApplicationContext) { 192 WebApplicationContext wac = (WebApplicationContext) context; 193 ServletContext servletContext = wac.getServletContext(); 194 if (!(servletContext instanceof MockServletContext)) { 195 throw new IllegalStateException(String.format( 196 "The WebApplicationContext for test context %s must be configured with a MockServletContext.", 197 testContext)); 198 } 199 200 if (logger.isDebugEnabled()) { 201 logger.debug(String.format( 202 "Setting up MockHttpServletRequest, MockHttpServletResponse, ServletWebRequest, and RequestContextHolder for test context %s.", 203 testContext)); 204 } 205 206 MockServletContext mockServletContext = (MockServletContext) servletContext; 207 MockHttpServletRequest request = new MockHttpServletRequest(mockServletContext); 208 request.setAttribute(CREATED_BY_THE_TESTCONTEXT_FRAMEWORK, Boolean.TRUE); 209 MockHttpServletResponse response = new MockHttpServletResponse(); 210 ServletWebRequest servletWebRequest = new ServletWebRequest(request, response); 211 212 RequestContextHolder.setRequestAttributes(servletWebRequest); 213 testContext.setAttribute(POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE); 214 testContext.setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE); 215 216 if (wac instanceof ConfigurableApplicationContext) { 217 @SuppressWarnings("resource") 218 ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) wac; 219 ConfigurableListableBeanFactory bf = configurableApplicationContext.getBeanFactory(); 220 bf.registerResolvableDependency(MockHttpServletResponse.class, response); 221 bf.registerResolvableDependency(ServletWebRequest.class, servletWebRequest); 222 } 223 } 224 } 225 226}