001/* 002 * Copyright 2002-2019 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.web.servlet; 018 019import java.util.ArrayList; 020import java.util.List; 021 022import javax.servlet.AsyncContext; 023import javax.servlet.DispatcherType; 024import javax.servlet.Filter; 025import javax.servlet.ServletContext; 026import javax.servlet.ServletResponse; 027import javax.servlet.http.HttpServletResponse; 028import javax.servlet.http.HttpServletResponseWrapper; 029 030import org.springframework.beans.Mergeable; 031import org.springframework.lang.Nullable; 032import org.springframework.mock.web.MockFilterChain; 033import org.springframework.mock.web.MockHttpServletRequest; 034import org.springframework.mock.web.MockHttpServletResponse; 035import org.springframework.util.Assert; 036import org.springframework.web.context.request.RequestAttributes; 037import org.springframework.web.context.request.RequestContextHolder; 038import org.springframework.web.context.request.ServletRequestAttributes; 039import org.springframework.web.servlet.DispatcherServlet; 040 041/** 042 * <strong>Main entry point for server-side Spring MVC test support.</strong> 043 * 044 * <h3>Example</h3> 045 * 046 * <pre class="code"> 047 * import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 048 * import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 049 * import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*; 050 * 051 * // ... 052 * 053 * WebApplicationContext wac = ...; 054 * 055 * MockMvc mockMvc = webAppContextSetup(wac).build(); 056 * 057 * mockMvc.perform(get("/form")) 058 * .andExpect(status().isOk()) 059 * .andExpect(content().mimeType("text/html")) 060 * .andExpect(forwardedUrl("/WEB-INF/layouts/main.jsp")); 061 * </pre> 062 * 063 * @author Rossen Stoyanchev 064 * @author Rob Winch 065 * @author Sam Brannen 066 * @since 3.2 067 */ 068public final class MockMvc { 069 070 static final String MVC_RESULT_ATTRIBUTE = MockMvc.class.getName().concat(".MVC_RESULT_ATTRIBUTE"); 071 072 private final TestDispatcherServlet servlet; 073 074 private final Filter[] filters; 075 076 private final ServletContext servletContext; 077 078 @Nullable 079 private RequestBuilder defaultRequestBuilder; 080 081 private List<ResultMatcher> defaultResultMatchers = new ArrayList<>(); 082 083 private List<ResultHandler> defaultResultHandlers = new ArrayList<>(); 084 085 086 /** 087 * Private constructor, not for direct instantiation. 088 * @see org.springframework.test.web.servlet.setup.MockMvcBuilders 089 */ 090 MockMvc(TestDispatcherServlet servlet, Filter... filters) { 091 Assert.notNull(servlet, "DispatcherServlet is required"); 092 Assert.notNull(filters, "Filters cannot be null"); 093 Assert.noNullElements(filters, "Filters cannot contain null values"); 094 095 this.servlet = servlet; 096 this.filters = filters; 097 this.servletContext = servlet.getServletContext(); 098 } 099 100 101 /** 102 * A default request builder merged into every performed request. 103 * @see org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder#defaultRequest(RequestBuilder) 104 */ 105 void setDefaultRequest(@Nullable RequestBuilder requestBuilder) { 106 this.defaultRequestBuilder = requestBuilder; 107 } 108 109 /** 110 * Expectations to assert after every performed request. 111 * @see org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder#alwaysExpect(ResultMatcher) 112 */ 113 void setGlobalResultMatchers(List<ResultMatcher> resultMatchers) { 114 Assert.notNull(resultMatchers, "ResultMatcher List is required"); 115 this.defaultResultMatchers = resultMatchers; 116 } 117 118 /** 119 * General actions to apply after every performed request. 120 * @see org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder#alwaysDo(ResultHandler) 121 */ 122 void setGlobalResultHandlers(List<ResultHandler> resultHandlers) { 123 Assert.notNull(resultHandlers, "ResultHandler List is required"); 124 this.defaultResultHandlers = resultHandlers; 125 } 126 127 /** 128 * Return the underlying {@link DispatcherServlet} instance that this 129 * {@code MockMvc} was initialized with. 130 * <p>This is intended for use in custom request processing scenario where a 131 * request handling component happens to delegate to the {@code DispatcherServlet} 132 * at runtime and therefore needs to be injected with it. 133 * <p>For most processing scenarios, simply use {@link MockMvc#perform}, 134 * or if you need to configure the {@code DispatcherServlet}, provide a 135 * {@link DispatcherServletCustomizer} to the {@code MockMvcBuilder}. 136 * @since 5.1 137 */ 138 public DispatcherServlet getDispatcherServlet() { 139 return this.servlet; 140 } 141 142 143 /** 144 * Perform a request and return a type that allows chaining further 145 * actions, such as asserting expectations, on the result. 146 * @param requestBuilder used to prepare the request to execute; 147 * see static factory methods in 148 * {@link org.springframework.test.web.servlet.request.MockMvcRequestBuilders} 149 * @return an instance of {@link ResultActions} (never {@code null}) 150 * @see org.springframework.test.web.servlet.request.MockMvcRequestBuilders 151 * @see org.springframework.test.web.servlet.result.MockMvcResultMatchers 152 */ 153 public ResultActions perform(RequestBuilder requestBuilder) throws Exception { 154 if (this.defaultRequestBuilder != null && requestBuilder instanceof Mergeable) { 155 requestBuilder = (RequestBuilder) ((Mergeable) requestBuilder).merge(this.defaultRequestBuilder); 156 } 157 158 MockHttpServletRequest request = requestBuilder.buildRequest(this.servletContext); 159 160 AsyncContext asyncContext = request.getAsyncContext(); 161 MockHttpServletResponse mockResponse; 162 HttpServletResponse servletResponse; 163 if (asyncContext != null) { 164 servletResponse = (HttpServletResponse) asyncContext.getResponse(); 165 mockResponse = unwrapResponseIfNecessary(servletResponse); 166 } 167 else { 168 mockResponse = new MockHttpServletResponse(); 169 servletResponse = mockResponse; 170 } 171 172 if (requestBuilder instanceof SmartRequestBuilder) { 173 request = ((SmartRequestBuilder) requestBuilder).postProcessRequest(request); 174 } 175 176 MvcResult mvcResult = new DefaultMvcResult(request, mockResponse); 177 request.setAttribute(MVC_RESULT_ATTRIBUTE, mvcResult); 178 179 RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); 180 RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request, servletResponse)); 181 182 MockFilterChain filterChain = new MockFilterChain(this.servlet, this.filters); 183 filterChain.doFilter(request, servletResponse); 184 185 if (DispatcherType.ASYNC.equals(request.getDispatcherType()) && 186 asyncContext != null && !request.isAsyncStarted()) { 187 asyncContext.complete(); 188 } 189 190 applyDefaultResultActions(mvcResult); 191 RequestContextHolder.setRequestAttributes(previousAttributes); 192 193 return new ResultActions() { 194 @Override 195 public ResultActions andExpect(ResultMatcher matcher) throws Exception { 196 matcher.match(mvcResult); 197 return this; 198 } 199 @Override 200 public ResultActions andDo(ResultHandler handler) throws Exception { 201 handler.handle(mvcResult); 202 return this; 203 } 204 @Override 205 public MvcResult andReturn() { 206 return mvcResult; 207 } 208 }; 209 } 210 211 private MockHttpServletResponse unwrapResponseIfNecessary(ServletResponse servletResponse) { 212 while (servletResponse instanceof HttpServletResponseWrapper) { 213 servletResponse = ((HttpServletResponseWrapper) servletResponse).getResponse(); 214 } 215 Assert.isInstanceOf(MockHttpServletResponse.class, servletResponse); 216 return (MockHttpServletResponse) servletResponse; 217 } 218 219 private void applyDefaultResultActions(MvcResult mvcResult) throws Exception { 220 for (ResultMatcher matcher : this.defaultResultMatchers) { 221 matcher.match(mvcResult); 222 } 223 for (ResultHandler handler : this.defaultResultHandlers) { 224 handler.handle(mvcResult); 225 } 226 } 227 228}