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}