001/*
002 * Copyright 2002-2018 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;
021import javax.servlet.DispatcherType;
022import javax.servlet.Filter;
023import javax.servlet.ServletContext;
024
025import org.springframework.beans.Mergeable;
026import org.springframework.mock.web.MockFilterChain;
027import org.springframework.mock.web.MockHttpServletRequest;
028import org.springframework.mock.web.MockHttpServletResponse;
029import org.springframework.util.Assert;
030import org.springframework.web.context.request.RequestAttributes;
031import org.springframework.web.context.request.RequestContextHolder;
032import org.springframework.web.context.request.ServletRequestAttributes;
033
034/**
035 * <strong>Main entry point for server-side Spring MVC test support.</strong>
036 *
037 * <h3>Example</h3>
038 *
039 * <pre class="code">
040 * import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
041 * import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
042 * import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
043 *
044 * // ...
045 *
046 * WebApplicationContext wac = ...;
047 *
048 * MockMvc mockMvc = webAppContextSetup(wac).build();
049 *
050 * mockMvc.perform(get("/form"))
051 *     .andExpect(status().isOk())
052 *     .andExpect(content().mimeType("text/html"))
053 *     .andExpect(forwardedUrl("/WEB-INF/layouts/main.jsp"));
054 * </pre>
055 *
056 * @author Rossen Stoyanchev
057 * @author Rob Winch
058 * @author Sam Brannen
059 * @since 3.2
060 */
061public final class MockMvc {
062
063        static final String MVC_RESULT_ATTRIBUTE = MockMvc.class.getName().concat(".MVC_RESULT_ATTRIBUTE");
064
065        private final TestDispatcherServlet servlet;
066
067        private final Filter[] filters;
068
069        private final ServletContext servletContext;
070
071        private RequestBuilder defaultRequestBuilder;
072
073        private List<ResultMatcher> defaultResultMatchers = new ArrayList<ResultMatcher>();
074
075        private List<ResultHandler> defaultResultHandlers = new ArrayList<ResultHandler>();
076
077
078        /**
079         * Private constructor, not for direct instantiation.
080         * @see org.springframework.test.web.servlet.setup.MockMvcBuilders
081         */
082        MockMvc(TestDispatcherServlet servlet, Filter[] filters, ServletContext servletContext) {
083                Assert.notNull(servlet, "DispatcherServlet is required");
084                Assert.notNull(filters, "Filters cannot be null");
085                Assert.noNullElements(filters, "Filters cannot contain null values");
086                Assert.notNull(servletContext, "ServletContext is required");
087
088                this.servlet = servlet;
089                this.filters = filters;
090                this.servletContext = servletContext;
091        }
092
093
094        /**
095         * A default request builder merged into every performed request.
096         * @see org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder#defaultRequest(RequestBuilder)
097         */
098        void setDefaultRequest(RequestBuilder requestBuilder) {
099                this.defaultRequestBuilder = requestBuilder;
100        }
101
102        /**
103         * Expectations to assert after every performed request.
104         * @see org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder#alwaysExpect(ResultMatcher)
105         */
106        void setGlobalResultMatchers(List<ResultMatcher> resultMatchers) {
107                Assert.notNull(resultMatchers, "ResultMatcher List is required");
108                this.defaultResultMatchers = resultMatchers;
109        }
110
111        /**
112         * General actions to apply after every performed request.
113         * @see org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder#alwaysDo(ResultHandler)
114         */
115        void setGlobalResultHandlers(List<ResultHandler> resultHandlers) {
116                Assert.notNull(resultHandlers, "ResultHandler List is required");
117                this.defaultResultHandlers = resultHandlers;
118        }
119
120        /**
121         * Perform a request and return a type that allows chaining further
122         * actions, such as asserting expectations, on the result.
123         * @param requestBuilder used to prepare the request to execute;
124         * see static factory methods in
125         * {@link org.springframework.test.web.servlet.request.MockMvcRequestBuilders}
126         * @return an instance of {@link ResultActions} (never {@code null})
127         * @see org.springframework.test.web.servlet.request.MockMvcRequestBuilders
128         * @see org.springframework.test.web.servlet.result.MockMvcResultMatchers
129         */
130        public ResultActions perform(RequestBuilder requestBuilder) throws Exception {
131                if (this.defaultRequestBuilder != null) {
132                        if (requestBuilder instanceof Mergeable) {
133                                requestBuilder = (RequestBuilder) ((Mergeable) requestBuilder).merge(this.defaultRequestBuilder);
134                        }
135                }
136
137                MockHttpServletRequest request = requestBuilder.buildRequest(this.servletContext);
138                MockHttpServletResponse response = new MockHttpServletResponse();
139
140                if (requestBuilder instanceof SmartRequestBuilder) {
141                        request = ((SmartRequestBuilder) requestBuilder).postProcessRequest(request);
142                }
143
144                final MvcResult mvcResult = new DefaultMvcResult(request, response);
145                request.setAttribute(MVC_RESULT_ATTRIBUTE, mvcResult);
146
147                RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
148                RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request, response));
149
150                MockFilterChain filterChain = new MockFilterChain(this.servlet, this.filters);
151                filterChain.doFilter(request, response);
152
153                if (DispatcherType.ASYNC.equals(request.getDispatcherType()) &&
154                                request.getAsyncContext() != null && !request.isAsyncStarted()) {
155                        request.getAsyncContext().complete();
156                }
157
158                applyDefaultResultActions(mvcResult);
159                RequestContextHolder.setRequestAttributes(previousAttributes);
160
161                return new ResultActions() {
162                        @Override
163                        public ResultActions andExpect(ResultMatcher matcher) throws Exception {
164                                matcher.match(mvcResult);
165                                return this;
166                        }
167                        @Override
168                        public ResultActions andDo(ResultHandler handler) throws Exception {
169                                handler.handle(mvcResult);
170                                return this;
171                        }
172                        @Override
173                        public MvcResult andReturn() {
174                                return mvcResult;
175                        }
176                };
177        }
178
179        private void applyDefaultResultActions(MvcResult mvcResult) throws Exception {
180                for (ResultMatcher matcher : this.defaultResultMatchers) {
181                        matcher.match(mvcResult);
182                }
183                for (ResultHandler handler : this.defaultResultHandlers) {
184                        handler.handle(mvcResult);
185                }
186        }
187
188}