Skip to content

Commit aa0903e

Browse files
rwinchrstoyanchev
authored andcommitted
Add support for using Servlet Filter's
This change makes it possible to add Servlet Filter's to the processing chain in server-side Spring MVC testing.
1 parent 120c631 commit aa0903e

File tree

10 files changed

+1057
-111
lines changed

10 files changed

+1057
-111
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ Test binding failure by pointing to Spring MVC XML-based context configuration:
3535
.andExpect(model().attributeHasErrors("formBean"))
3636
.andExpect(view().name("form"));
3737

38+
Test that a `Filter` performs a redirect:
39+
40+
MockMvcBuilders.standaloneSetup(new TestController())
41+
.addFilters(new LoginFilter()).build()
42+
.perform(get("/form")
43+
.andExpect(redirectedUrl("/login"));
44+
3845
Test serving a resource by pointing to Spring MVC Java-based application configuration:
3946

4047
MockMvcBuilders.annotationConfigSetup(TestConfiguration.class).build()
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.mock.web;
18+
19+
import java.io.IOException;
20+
import java.util.Arrays;
21+
import java.util.Collections;
22+
import java.util.Iterator;
23+
import java.util.List;
24+
25+
import javax.servlet.Filter;
26+
import javax.servlet.FilterChain;
27+
import javax.servlet.FilterConfig;
28+
import javax.servlet.Servlet;
29+
import javax.servlet.ServletException;
30+
import javax.servlet.ServletRequest;
31+
import javax.servlet.ServletResponse;
32+
33+
import org.springframework.util.Assert;
34+
import org.springframework.util.ObjectUtils;
35+
36+
/**
37+
* Mock implementation of the {@link javax.servlet.FilterChain} interface. Used
38+
* for testing the web framework; also useful for testing custom
39+
* {@link javax.servlet.Filter} implementations.
40+
*
41+
* <p>A {@link MockFilterChain} can be configured with one or more filters and a
42+
* Servlet to be invoked. When the chain is invoked, it invokes in turn all
43+
* filters and the Servlet and saves the request and response. Subsequent
44+
* invocations raise an {@link IllegalStateException} unless {@link #reset()} is
45+
* called.
46+
*
47+
* @author Juergen Hoeller
48+
* @author Rob Winch
49+
* @author Rossen Stoyanchev
50+
*
51+
* @since 2.0.3
52+
* @see MockFilterConfig
53+
* @see PassThroughFilterChain
54+
*/
55+
public class MockFilterChain implements FilterChain {
56+
57+
private ServletRequest request;
58+
59+
private ServletResponse response;
60+
61+
private final List<Filter> filters;
62+
63+
private Iterator<Filter> iterator;
64+
65+
66+
/**
67+
* Register a single do-nothing {@link Filter} implementation. The first
68+
* invocation saves the request and response. Subsequent invocations raise
69+
* an {@link IllegalStateException} unless {@link #reset()} is called.
70+
*/
71+
public MockFilterChain() {
72+
this.filters = Collections.emptyList();
73+
}
74+
75+
/**
76+
* Create a FilterChain with a Servlet.
77+
*
78+
* @param servlet the Servlet to invoke
79+
* @since 3.2
80+
*/
81+
public MockFilterChain(Servlet servlet) {
82+
this.filters = initFilterList(servlet);
83+
}
84+
85+
/**
86+
* Create a {@code FilterChain} with Filter's and a Servlet.
87+
*
88+
* @param servlet the {@link Servlet} to invoke in this {@link FilterChain}
89+
* @param filters the {@link Filter}'s to invoke in this {@link FilterChain}
90+
* @since 3.2
91+
*/
92+
public MockFilterChain(Servlet servlet, Filter... filters) {
93+
Assert.notNull(filters, "filters cannot be null");
94+
Assert.noNullElements(filters, "filters cannot contain null values");
95+
this.filters = initFilterList(servlet, filters);
96+
}
97+
98+
private static List<Filter> initFilterList(Servlet servlet, Filter... filters) {
99+
Filter[] allFilters = ObjectUtils.addObjectToArray(filters, new ServletFilterProxy(servlet));
100+
return Arrays.asList(allFilters);
101+
}
102+
103+
/**
104+
* Return the request that {@link #doFilter} has been called with.
105+
*/
106+
public ServletRequest getRequest() {
107+
return this.request;
108+
}
109+
110+
/**
111+
* Return the response that {@link #doFilter} has been called with.
112+
*/
113+
public ServletResponse getResponse() {
114+
return this.response;
115+
}
116+
117+
/**
118+
* Invoke registered {@link Filter}s and/or {@link Servlet} also saving the
119+
* request and response.
120+
*/
121+
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
122+
Assert.notNull(request, "Request must not be null");
123+
Assert.notNull(response, "Response must not be null");
124+
125+
if (this.request != null) {
126+
throw new IllegalStateException("This FilterChain has already been called!");
127+
}
128+
129+
if (this.iterator == null) {
130+
this.iterator = this.filters.iterator();
131+
}
132+
133+
if (this.iterator.hasNext()) {
134+
Filter nextFilter = this.iterator.next();
135+
nextFilter.doFilter(request, response, this);
136+
}
137+
138+
this.request = request;
139+
this.response = response;
140+
}
141+
142+
/**
143+
* Reset the {@link MockFilterChain} allowing it to be invoked again.
144+
*/
145+
public void reset() {
146+
this.request = null;
147+
this.response = null;
148+
this.iterator = null;
149+
}
150+
151+
152+
/**
153+
* A filter that simply delegates to a Servlet.
154+
*/
155+
private static class ServletFilterProxy implements Filter {
156+
157+
private final Servlet delegateServlet;
158+
159+
private ServletFilterProxy(Servlet servlet) {
160+
Assert.notNull(servlet, "servlet cannot be null");
161+
this.delegateServlet = servlet;
162+
}
163+
164+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
165+
throws IOException, ServletException {
166+
167+
this.delegateServlet.service(request, response);
168+
}
169+
170+
public void init(FilterConfig filterConfig) throws ServletException {
171+
}
172+
173+
public void destroy() {
174+
}
175+
176+
@Override
177+
public String toString() {
178+
return this.delegateServlet.toString();
179+
}
180+
}
181+
182+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.test.web.server;
17+
18+
import org.springframework.mock.web.MockHttpServletRequest;
19+
import org.springframework.mock.web.MockHttpServletResponse;
20+
import org.springframework.web.servlet.FlashMap;
21+
import org.springframework.web.servlet.HandlerInterceptor;
22+
import org.springframework.web.servlet.ModelAndView;
23+
import org.springframework.web.servlet.support.RequestContextUtils;
24+
25+
/**
26+
* A default implementation of {@link MvcResult}
27+
*
28+
* @author Rossen Stoyanchev
29+
* @author Rob Winch
30+
*/
31+
public class DefaultMvcResult implements MvcResult {
32+
33+
private final MockHttpServletRequest mockRequest;
34+
35+
private final MockHttpServletResponse mockResponse;
36+
37+
private Object handler;
38+
39+
private HandlerInterceptor[] interceptors;
40+
41+
private ModelAndView modelAndView;
42+
43+
private Exception resolvedException;
44+
45+
46+
public DefaultMvcResult(MockHttpServletRequest request, MockHttpServletResponse response) {
47+
this.mockRequest = request;
48+
this.mockResponse = response;
49+
}
50+
51+
public MockHttpServletResponse getResponse() {
52+
return mockResponse;
53+
}
54+
55+
public MockHttpServletRequest getRequest() {
56+
return mockRequest;
57+
}
58+
59+
public Object getHandler() {
60+
return this.handler;
61+
}
62+
63+
public void setHandler(Object handler) {
64+
this.handler = handler;
65+
}
66+
67+
public HandlerInterceptor[] getInterceptors() {
68+
return this.interceptors;
69+
}
70+
71+
public void setInterceptors(HandlerInterceptor[] interceptors) {
72+
this.interceptors = interceptors;
73+
}
74+
75+
public Exception getResolvedException() {
76+
return this.resolvedException;
77+
}
78+
79+
public void setResolvedException(Exception resolvedException) {
80+
this.resolvedException = resolvedException;
81+
}
82+
83+
public ModelAndView getModelAndView() {
84+
return this.modelAndView;
85+
}
86+
87+
public void setModelAndView(ModelAndView mav) {
88+
this.modelAndView = mav;
89+
}
90+
91+
public FlashMap getFlashMap() {
92+
return RequestContextUtils.getOutputFlashMap(mockRequest);
93+
}
94+
95+
}

src/main/java/org/springframework/test/web/server/MockMvc.java

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@
1616

1717
package org.springframework.test.web.server;
1818

19+
import java.io.IOException;
20+
1921
import javax.servlet.ServletContext;
22+
import javax.servlet.ServletException;
2023

24+
import org.springframework.mock.web.MockFilterChain;
2125
import org.springframework.mock.web.MockHttpServletRequest;
2226
import org.springframework.mock.web.MockHttpServletResponse;
2327
import org.springframework.util.Assert;
@@ -40,21 +44,26 @@
4044
* </pre>
4145
*
4246
* @author Rossen Stoyanchev
47+
* @author Rob Winch
4348
*/
4449
public class MockMvc {
4550

46-
private final TestDispatcherServlet dispatcherServlet;
51+
static String MVC_RESULT_ATTRIBUTE = MockMvc.class.getName().concat(".MVC_RESULT_ATTRIBUTE");
52+
53+
private final MockFilterChain filterChain;
4754

4855
private final ServletContext servletContext;
4956

5057
/**
5158
* Protected constructor not for direct instantiation.
5259
* @see org.springframework.test.web.server.setup.MockMvcBuilders
5360
*/
54-
protected MockMvc(TestDispatcherServlet dispatcherServlet) {
55-
this.dispatcherServlet = dispatcherServlet;
56-
this.servletContext = this.dispatcherServlet.getServletContext();
57-
Assert.notNull(this.servletContext, "A ServletContext is required");
61+
protected MockMvc(MockFilterChain filterChain, ServletContext servletContext) {
62+
Assert.notNull(servletContext, "A ServletContext is required");
63+
Assert.notNull(filterChain, "A MockFilterChain is required");
64+
65+
this.filterChain = filterChain;
66+
this.servletContext = servletContext;
5867
}
5968

6069
/**
@@ -69,29 +78,31 @@ protected MockMvc(TestDispatcherServlet dispatcherServlet) {
6978
* @see org.springframework.test.web.server.request.MockMvcRequestBuilders
7079
* @see org.springframework.test.web.server.result.MockMvcResultMatchers
7180
*/
72-
public ResultActions perform(RequestBuilder requestBuilder) throws Exception {
81+
public ResultActions perform(RequestBuilder requestBuilder) throws IOException, ServletException {
7382

7483
MockHttpServletRequest request = requestBuilder.buildRequest(this.servletContext);
7584
MockHttpServletResponse response = new MockHttpServletResponse();
7685

77-
this.dispatcherServlet.service(request, response);
86+
final MvcResult mvcResult = new DefaultMvcResult(request, response);
87+
request.setAttribute(MVC_RESULT_ATTRIBUTE, mvcResult);
7888

79-
final MvcResult result = this.dispatcherServlet.getMvcResult(request);
89+
this.filterChain.reset();
90+
this.filterChain.doFilter(request, response);
8091

8192
return new ResultActions() {
8293

8394
public ResultActions andExpect(ResultMatcher matcher) throws Exception {
84-
matcher.match(result);
95+
matcher.match(mvcResult);
8596
return this;
8697
}
8798

8899
public ResultActions andDo(ResultHandler printer) throws Exception {
89-
printer.handle(result);
100+
printer.handle(mvcResult);
90101
return this;
91102
}
92103

93104
public MvcResult andReturn() {
94-
return result;
105+
return mvcResult;
95106
}
96107
};
97108
}

0 commit comments

Comments
 (0)