Skip to content

Commit 71b63cd

Browse files
committed
Update MockMvcConfigurer support
This is a follow-up on the commit introducing MockMvcConfigurer: c2b0fac This commit refines the MockMvcConfigurer contract to use (the new) ConfigurableMockMvcBuilder hence not requiring downcasting to AbstractMockMvcBuilder. The same also no longer passes the "default" RequestBuilder which would also require a downcast, but rather allows a RequestPostProcessor to be returned so that a 3rd party framework or application can modify any property of every performed MockHttpServletRequest. To make this possible the new SmartRequestBuilder interface separates request building from request post processing while the new ConfigurableSmartRequestBuilder allows adding a RequestPostProcessor to a MockMvcBuilder. Issue: SPR-11497
1 parent 988499f commit 71b63cd

File tree

10 files changed

+373
-134
lines changed

10 files changed

+373
-134
lines changed

spring-test/src/main/java/org/springframework/test/web/servlet/MockMvc.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ public ResultActions perform(RequestBuilder requestBuilder) throws Exception {
134134
MockHttpServletRequest request = requestBuilder.buildRequest(this.servletContext);
135135
MockHttpServletResponse response = new MockHttpServletResponse();
136136

137+
if (requestBuilder instanceof SmartRequestBuilder) {
138+
request = ((SmartRequestBuilder) requestBuilder).postProcessRequest(request);
139+
}
140+
137141
final MvcResult mvcResult = new DefaultMvcResult(request, response);
138142
request.setAttribute(MVC_RESULT_ATTRIBUTE, mvcResult);
139143

spring-test/src/main/java/org/springframework/test/web/servlet/RequestBuilder.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
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+
*/
116
package org.springframework.test.web.servlet;
217

318
import javax.servlet.ServletContext;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2002-2014 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.servlet;
17+
18+
import org.springframework.mock.web.MockHttpServletRequest;
19+
import org.springframework.test.web.servlet.request.RequestPostProcessor;
20+
21+
/**
22+
* Extended variant of a {@link RequestBuilder} that applies its
23+
* {@link org.springframework.test.web.servlet.request.RequestPostProcessor}s
24+
* as a separate step from the {@link #buildRequest} method.
25+
*
26+
* @author Rossen Stoyanchev
27+
* @since 4.1
28+
*/
29+
public interface SmartRequestBuilder extends RequestBuilder {
30+
31+
/**
32+
* Apply request post processing. Typically that means invoking one or more
33+
* {@link org.springframework.test.web.servlet.request.RequestPostProcessor}s.
34+
*
35+
* @param request the request to initialize
36+
* @return the request to use, either the one passed in or a wrapped one
37+
*/
38+
MockHttpServletRequest postProcessRequest(MockHttpServletRequest request);
39+
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2002-2014 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.servlet.request;
17+
18+
import org.springframework.test.web.servlet.SmartRequestBuilder;
19+
20+
21+
/**
22+
* An extension of {@link org.springframework.test.web.servlet.SmartRequestBuilder
23+
* SmartRequestBuilder} that can be configured with {@link RequestPostProcessor}s.
24+
*
25+
* @author Rossen Stoyanchev
26+
* @since 4.1
27+
*/
28+
public interface ConfigurableSmartRequestBuilder<B extends ConfigurableSmartRequestBuilder<B>>
29+
extends SmartRequestBuilder {
30+
31+
/**
32+
* Add the given {@code RequestPostProcessor}.
33+
*/
34+
B with(RequestPostProcessor requestPostProcessor);
35+
36+
}

spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
import org.springframework.mock.web.MockHttpServletResponse;
4040
import org.springframework.mock.web.MockHttpSession;
4141
import org.springframework.test.web.servlet.MockMvc;
42-
import org.springframework.test.web.servlet.RequestBuilder;
4342
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
4443
import org.springframework.util.Assert;
4544
import org.springframework.util.LinkedMultiValueMap;
@@ -68,7 +67,9 @@
6867
* @author Arjen Poutsma
6968
* @since 3.2
7069
*/
71-
public class MockHttpServletRequestBuilder implements RequestBuilder, Mergeable {
70+
public class MockHttpServletRequestBuilder
71+
implements ConfigurableSmartRequestBuilder<MockHttpServletRequestBuilder>, Mergeable {
72+
7273

7374
private final HttpMethod method;
7475

@@ -421,6 +422,7 @@ public MockHttpServletRequestBuilder secure(boolean secure){
421422
* and be made accessible through static factory methods.
422423
* @param postProcessor a post-processor to add
423424
*/
425+
@Override
424426
public MockHttpServletRequestBuilder with(RequestPostProcessor postProcessor) {
425427
Assert.notNull(postProcessor, "postProcessor is required");
426428
this.postProcessors.add(postProcessor);
@@ -621,14 +623,6 @@ public final MockHttpServletRequest buildRequest(ServletContext servletContext)
621623
FlashMapManager flashMapManager = getFlashMapManager(request);
622624
flashMapManager.saveOutputFlashMap(flashMap, request, new MockHttpServletResponse());
623625

624-
// Apply post-processors at the very end
625-
for (RequestPostProcessor postProcessor : this.postProcessors) {
626-
request = postProcessor.postProcessRequest(request);
627-
if (request == null) {
628-
throw new IllegalStateException("Post-processor [" + postProcessor.getClass().getName() + "] returned null");
629-
}
630-
}
631-
632626
request.setAsyncSupported(true);
633627

634628
return request;
@@ -675,6 +669,18 @@ private FlashMapManager getFlashMapManager(MockHttpServletRequest request) {
675669
return (flashMapManager != null ? flashMapManager : new SessionFlashMapManager());
676670
}
677671

672+
@Override
673+
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
674+
for (RequestPostProcessor postProcessor : this.postProcessors) {
675+
request = postProcessor.postProcessRequest(request);
676+
if (request == null) {
677+
throw new IllegalStateException(
678+
"Post-processor [" + postProcessor.getClass().getName() + "] returned null");
679+
}
680+
}
681+
return request;
682+
}
683+
678684
private static <T> void addToMultiValueMap(MultiValueMap<String, T> map, String name, T[] values) {
679685
Assert.hasLength(name, "'name' must not be empty");
680686
Assert.notNull(values, "'values' is required");

spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java

Lines changed: 20 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@
1717
package org.springframework.test.web.servlet.setup;
1818

1919
import org.springframework.mock.web.MockServletConfig;
20-
import org.springframework.test.web.servlet.*;
20+
import org.springframework.test.web.servlet.MockMvc;
21+
import org.springframework.test.web.servlet.MockMvcBuilderSupport;
22+
import org.springframework.test.web.servlet.RequestBuilder;
23+
import org.springframework.test.web.servlet.ResultHandler;
24+
import org.springframework.test.web.servlet.ResultMatcher;
25+
import org.springframework.test.web.servlet.request.ConfigurableSmartRequestBuilder;
26+
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
27+
import org.springframework.test.web.servlet.request.RequestPostProcessor;
2128
import org.springframework.util.Assert;
2229
import org.springframework.web.context.WebApplicationContext;
2330

@@ -38,7 +45,7 @@
3845
* @since 4.0
3946
*/
4047
public abstract class AbstractMockMvcBuilder<B extends AbstractMockMvcBuilder<B>>
41-
extends MockMvcBuilderSupport implements MockMvcBuilder {
48+
extends MockMvcBuilderSupport implements ConfigurableMockMvcBuilder<B> {
4249

4350
private List<Filter> filters = new ArrayList<Filter>();
4451

@@ -53,27 +60,6 @@ public abstract class AbstractMockMvcBuilder<B extends AbstractMockMvcBuilder<B>
5360
private final List<MockMvcConfigurer> configurers = new ArrayList<MockMvcConfigurer>(4);
5461

5562

56-
57-
/**
58-
* Add filters mapped to any request (i.e. "/*"). For example:
59-
*
60-
* <pre class="code">
61-
* mockMvcBuilder.addFilters(springSecurityFilterChain);
62-
* </pre>
63-
*
64-
* <p>is the equivalent of the following web.xml configuration:
65-
*
66-
* <pre class="code">
67-
* &lt;filter-mapping&gt;
68-
* &lt;filter-name&gt;springSecurityFilterChain&lt;/filter-name&gt;
69-
* &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
70-
* &lt;/filter-mapping&gt;
71-
* </pre>
72-
*
73-
* <p>Filters will be invoked in the order in which they are provided.
74-
*
75-
* @param filters the filters to add
76-
*/
7763
@SuppressWarnings("unchecked")
7864
public final <T extends B> T addFilters(Filter... filters) {
7965
Assert.notNull(filters, "filters cannot be null");
@@ -85,27 +71,6 @@ public final <T extends B> T addFilters(Filter... filters) {
8571
return (T) this;
8672
}
8773

88-
/**
89-
* Add a filter mapped to a specific set of patterns. For example:
90-
*
91-
* <pre class="code">
92-
* mockMvcBuilder.addFilters(myResourceFilter, "/resources/*");
93-
* </pre>
94-
*
95-
* <p>is the equivalent of:
96-
*
97-
* <pre class="code">
98-
* &lt;filter-mapping&gt;
99-
* &lt;filter-name&gt;myResourceFilter&lt;/filter-name&gt;
100-
* &lt;url-pattern&gt;/resources/*&lt;/url-pattern&gt;
101-
* &lt;/filter-mapping&gt;
102-
* </pre>
103-
*
104-
* <p>Filters will be invoked in the order in which they are provided.
105-
*
106-
* @param filter the filter to add
107-
* @param urlPatterns URL patterns to map to; if empty, "/*" is used by default
108-
*/
10974
@SuppressWarnings("unchecked")
11075
public final <T extends B> T addFilter(Filter filter, String... urlPatterns) {
11176

@@ -120,70 +85,32 @@ public final <T extends B> T addFilter(Filter filter, String... urlPatterns) {
12085
return (T) this;
12186
}
12287

123-
/**
124-
* Define default request properties that should be merged into all
125-
* performed requests. In effect this provides a mechanism for defining
126-
* common initialization for all requests such as the content type, request
127-
* parameters, session attributes, and any other request property.
128-
*
129-
* <p>Properties specified at the time of performing a request override the
130-
* default properties defined here.
131-
*
132-
* @param requestBuilder a RequestBuilder; see static factory methods in
133-
* {@link org.springframework.test.web.servlet.request.MockMvcRequestBuilders}
134-
* .
135-
*/
13688
@SuppressWarnings("unchecked")
13789
public final <T extends B> T defaultRequest(RequestBuilder requestBuilder) {
13890
this.defaultRequestBuilder = requestBuilder;
13991
return (T) this;
14092
}
14193

142-
/**
143-
* Define a global expectation that should <em>always</em> be applied to
144-
* every response. For example, status code 200 (OK), content type
145-
* {@code "application/json"}, etc.
146-
*
147-
* @param resultMatcher a ResultMatcher; see static factory methods in
148-
* {@link org.springframework.test.web.servlet.result.MockMvcResultMatchers}
149-
*/
15094
@SuppressWarnings("unchecked")
15195
public final <T extends B> T alwaysExpect(ResultMatcher resultMatcher) {
15296
this.globalResultMatchers.add(resultMatcher);
15397
return (T) this;
15498
}
15599

156-
/**
157-
* Define a global action that should <em>always</em> be applied to every
158-
* response. For example, writing detailed information about the performed
159-
* request and resulting response to {@code System.out}.
160-
*
161-
* @param resultHandler a ResultHandler; see static factory methods in
162-
* {@link org.springframework.test.web.servlet.result.MockMvcResultHandlers}
163-
*/
164100
@SuppressWarnings("unchecked")
165101
public final <T extends B> T alwaysDo(ResultHandler resultHandler) {
166102
this.globalResultHandlers.add(resultHandler);
167103
return (T) this;
168104
}
169105

170-
/**
171-
* Whether to enable the DispatcherServlet property
172-
* {@link org.springframework.web.servlet.DispatcherServlet#setDispatchOptionsRequest
173-
* dispatchOptionsRequest} which allows processing of HTTP OPTIONS requests.
174-
*/
175106
@SuppressWarnings("unchecked")
176107
public final <T extends B> T dispatchOptions(boolean dispatchOptions) {
177108
this.dispatchOptions = dispatchOptions;
178109
return (T) this;
179110
}
180111

181-
/**
182-
* Add a {@code MockMvcConfigurer} which encapsulates ways to further configure
183-
* this MockMvcBuilder with some specific purpose in mind.
184-
*/
185112
@SuppressWarnings("unchecked")
186-
public final <T extends B> T add(MockMvcConfigurer configurer) {
113+
public final <T extends B> T apply(MockMvcConfigurer configurer) {
187114
configurer.afterConfigurerAdded(this);
188115
this.configurers.add(configurer);
189116
return (T) this;
@@ -202,7 +129,15 @@ public final MockMvc build() {
202129
MockServletConfig mockServletConfig = new MockServletConfig(servletContext);
203130

204131
for (MockMvcConfigurer configurer : this.configurers) {
205-
configurer.beforeMockMvcCreated(this, this.defaultRequestBuilder, wac);
132+
RequestPostProcessor processor = configurer.beforeMockMvcCreated(this, wac);
133+
if (processor != null) {
134+
if (this.defaultRequestBuilder == null) {
135+
this.defaultRequestBuilder = MockMvcRequestBuilders.get("/");
136+
}
137+
if (this.defaultRequestBuilder instanceof ConfigurableSmartRequestBuilder) {
138+
((ConfigurableSmartRequestBuilder) this.defaultRequestBuilder).with(processor);
139+
}
140+
}
206141
}
207142

208143
Filter[] filterArray = this.filters.toArray(new Filter[this.filters.size()]);
@@ -218,4 +153,4 @@ public final MockMvc build() {
218153
*/
219154
protected abstract WebApplicationContext initWebAppContext();
220155

221-
}
156+
}

0 commit comments

Comments
 (0)