From 71b63cd972c399733005535c508bb273ad0ad263 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 21 Jul 2014 13:43:45 -0400 Subject: [PATCH] Update MockMvcConfigurer support This is a follow-up on the commit introducing MockMvcConfigurer: https://github.com/spring-projects/spring-framework/commit/c2b0fac852dd9f865699fb374ad78543767fec05 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 --- .../test/web/servlet/MockMvc.java | 4 + .../test/web/servlet/RequestBuilder.java | 15 +++ .../test/web/servlet/SmartRequestBuilder.java | 40 ++++++ .../ConfigurableSmartRequestBuilder.java | 36 +++++ .../MockHttpServletRequestBuilder.java | 26 ++-- .../servlet/setup/AbstractMockMvcBuilder.java | 105 +++------------ .../setup/ConfigurableMockMvcBuilder.java | 127 ++++++++++++++++++ .../web/servlet/setup/MockMvcConfigurer.java | 39 +++--- .../setup/MockMvcConfigurerAdapter.java | 41 ++++++ ...ests.java => FrameworkExtensionTests.java} | 74 +++++++--- 10 files changed, 373 insertions(+), 134 deletions(-) create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/SmartRequestBuilder.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/request/ConfigurableSmartRequestBuilder.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/setup/ConfigurableMockMvcBuilder.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/setup/MockMvcConfigurerAdapter.java rename spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/{RequestBuilderTests.java => FrameworkExtensionTests.java} (56%) diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/MockMvc.java b/spring-test/src/main/java/org/springframework/test/web/servlet/MockMvc.java index a25452dc800..33bbd406bb9 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/MockMvc.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/MockMvc.java @@ -134,6 +134,10 @@ public final class MockMvc { MockHttpServletRequest request = requestBuilder.buildRequest(this.servletContext); MockHttpServletResponse response = new MockHttpServletResponse(); + if (requestBuilder instanceof SmartRequestBuilder) { + request = ((SmartRequestBuilder) requestBuilder).postProcessRequest(request); + } + final MvcResult mvcResult = new DefaultMvcResult(request, response); request.setAttribute(MVC_RESULT_ATTRIBUTE, mvcResult); diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/RequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/RequestBuilder.java index f82e6aae027..9754a96c6b5 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/RequestBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/RequestBuilder.java @@ -1,3 +1,18 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.springframework.test.web.servlet; import javax.servlet.ServletContext; diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/SmartRequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/SmartRequestBuilder.java new file mode 100644 index 00000000000..6835caab4a0 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/SmartRequestBuilder.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.test.web.servlet; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.test.web.servlet.request.RequestPostProcessor; + +/** + * Extended variant of a {@link RequestBuilder} that applies its + * {@link org.springframework.test.web.servlet.request.RequestPostProcessor}s + * as a separate step from the {@link #buildRequest} method. + * + * @author Rossen Stoyanchev + * @since 4.1 + */ +public interface SmartRequestBuilder extends RequestBuilder { + + /** + * Apply request post processing. Typically that means invoking one or more + * {@link org.springframework.test.web.servlet.request.RequestPostProcessor}s. + * + * @param request the request to initialize + * @return the request to use, either the one passed in or a wrapped one + */ + MockHttpServletRequest postProcessRequest(MockHttpServletRequest request); + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/request/ConfigurableSmartRequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/request/ConfigurableSmartRequestBuilder.java new file mode 100644 index 00000000000..cbb78c47239 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/request/ConfigurableSmartRequestBuilder.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.test.web.servlet.request; + +import org.springframework.test.web.servlet.SmartRequestBuilder; + + +/** + * An extension of {@link org.springframework.test.web.servlet.SmartRequestBuilder + * SmartRequestBuilder} that can be configured with {@link RequestPostProcessor}s. + * + * @author Rossen Stoyanchev + * @since 4.1 + */ +public interface ConfigurableSmartRequestBuilder> + extends SmartRequestBuilder { + + /** + * Add the given {@code RequestPostProcessor}. + */ + B with(RequestPostProcessor requestPostProcessor); + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java index 051d1ad0dd4..dafb0913178 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java @@ -39,7 +39,6 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.RequestBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; @@ -68,7 +67,9 @@ import org.springframework.web.util.UriUtils; * @author Arjen Poutsma * @since 3.2 */ -public class MockHttpServletRequestBuilder implements RequestBuilder, Mergeable { +public class MockHttpServletRequestBuilder + implements ConfigurableSmartRequestBuilder, Mergeable { + private final HttpMethod method; @@ -421,6 +422,7 @@ public class MockHttpServletRequestBuilder implements RequestBuilder, Mergeable * and be made accessible through static factory methods. * @param postProcessor a post-processor to add */ + @Override public MockHttpServletRequestBuilder with(RequestPostProcessor postProcessor) { Assert.notNull(postProcessor, "postProcessor is required"); this.postProcessors.add(postProcessor); @@ -621,14 +623,6 @@ public class MockHttpServletRequestBuilder implements RequestBuilder, Mergeable FlashMapManager flashMapManager = getFlashMapManager(request); flashMapManager.saveOutputFlashMap(flashMap, request, new MockHttpServletResponse()); - // Apply post-processors at the very end - for (RequestPostProcessor postProcessor : this.postProcessors) { - request = postProcessor.postProcessRequest(request); - if (request == null) { - throw new IllegalStateException("Post-processor [" + postProcessor.getClass().getName() + "] returned null"); - } - } - request.setAsyncSupported(true); return request; @@ -675,6 +669,18 @@ public class MockHttpServletRequestBuilder implements RequestBuilder, Mergeable return (flashMapManager != null ? flashMapManager : new SessionFlashMapManager()); } + @Override + public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { + for (RequestPostProcessor postProcessor : this.postProcessors) { + request = postProcessor.postProcessRequest(request); + if (request == null) { + throw new IllegalStateException( + "Post-processor [" + postProcessor.getClass().getName() + "] returned null"); + } + } + return request; + } + private static void addToMultiValueMap(MultiValueMap map, String name, T[] values) { Assert.hasLength(name, "'name' must not be empty"); Assert.notNull(values, "'values' is required"); diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java index f1bde2359cb..3bf75e7b732 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java @@ -17,7 +17,14 @@ package org.springframework.test.web.servlet.setup; import org.springframework.mock.web.MockServletConfig; -import org.springframework.test.web.servlet.*; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MockMvcBuilderSupport; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.ResultHandler; +import org.springframework.test.web.servlet.ResultMatcher; +import org.springframework.test.web.servlet.request.ConfigurableSmartRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.util.Assert; import org.springframework.web.context.WebApplicationContext; @@ -38,7 +45,7 @@ import java.util.List; * @since 4.0 */ public abstract class AbstractMockMvcBuilder> - extends MockMvcBuilderSupport implements MockMvcBuilder { + extends MockMvcBuilderSupport implements ConfigurableMockMvcBuilder { private List filters = new ArrayList(); @@ -53,27 +60,6 @@ public abstract class AbstractMockMvcBuilder private final List configurers = new ArrayList(4); - - /** - * Add filters mapped to any request (i.e. "/*"). For example: - * - *
-	 * mockMvcBuilder.addFilters(springSecurityFilterChain);
-	 * 
- * - *

is the equivalent of the following web.xml configuration: - * - *

-	 * <filter-mapping>
-	 *     <filter-name>springSecurityFilterChain</filter-name>
-	 *     <url-pattern>/*</url-pattern>
-	 * </filter-mapping>
-	 * 
- * - *

Filters will be invoked in the order in which they are provided. - * - * @param filters the filters to add - */ @SuppressWarnings("unchecked") public final T addFilters(Filter... filters) { Assert.notNull(filters, "filters cannot be null"); @@ -85,27 +71,6 @@ public abstract class AbstractMockMvcBuilder return (T) this; } - /** - * Add a filter mapped to a specific set of patterns. For example: - * - *

-	 * mockMvcBuilder.addFilters(myResourceFilter, "/resources/*");
-	 * 
- * - *

is the equivalent of: - * - *

-	 * <filter-mapping>
-	 *     <filter-name>myResourceFilter</filter-name>
-	 *     <url-pattern>/resources/*</url-pattern>
-	 * </filter-mapping>
-	 * 
- * - *

Filters will be invoked in the order in which they are provided. - * - * @param filter the filter to add - * @param urlPatterns URL patterns to map to; if empty, "/*" is used by default - */ @SuppressWarnings("unchecked") public final T addFilter(Filter filter, String... urlPatterns) { @@ -120,70 +85,32 @@ public abstract class AbstractMockMvcBuilder return (T) this; } - /** - * Define default request properties that should be merged into all - * performed requests. In effect this provides a mechanism for defining - * common initialization for all requests such as the content type, request - * parameters, session attributes, and any other request property. - * - *

Properties specified at the time of performing a request override the - * default properties defined here. - * - * @param requestBuilder a RequestBuilder; see static factory methods in - * {@link org.springframework.test.web.servlet.request.MockMvcRequestBuilders} - * . - */ @SuppressWarnings("unchecked") public final T defaultRequest(RequestBuilder requestBuilder) { this.defaultRequestBuilder = requestBuilder; return (T) this; } - /** - * Define a global expectation that should always be applied to - * every response. For example, status code 200 (OK), content type - * {@code "application/json"}, etc. - * - * @param resultMatcher a ResultMatcher; see static factory methods in - * {@link org.springframework.test.web.servlet.result.MockMvcResultMatchers} - */ @SuppressWarnings("unchecked") public final T alwaysExpect(ResultMatcher resultMatcher) { this.globalResultMatchers.add(resultMatcher); return (T) this; } - /** - * Define a global action that should always be applied to every - * response. For example, writing detailed information about the performed - * request and resulting response to {@code System.out}. - * - * @param resultHandler a ResultHandler; see static factory methods in - * {@link org.springframework.test.web.servlet.result.MockMvcResultHandlers} - */ @SuppressWarnings("unchecked") public final T alwaysDo(ResultHandler resultHandler) { this.globalResultHandlers.add(resultHandler); return (T) this; } - /** - * Whether to enable the DispatcherServlet property - * {@link org.springframework.web.servlet.DispatcherServlet#setDispatchOptionsRequest - * dispatchOptionsRequest} which allows processing of HTTP OPTIONS requests. - */ @SuppressWarnings("unchecked") public final T dispatchOptions(boolean dispatchOptions) { this.dispatchOptions = dispatchOptions; return (T) this; } - /** - * Add a {@code MockMvcConfigurer} which encapsulates ways to further configure - * this MockMvcBuilder with some specific purpose in mind. - */ @SuppressWarnings("unchecked") - public final T add(MockMvcConfigurer configurer) { + public final T apply(MockMvcConfigurer configurer) { configurer.afterConfigurerAdded(this); this.configurers.add(configurer); return (T) this; @@ -202,7 +129,15 @@ public abstract class AbstractMockMvcBuilder MockServletConfig mockServletConfig = new MockServletConfig(servletContext); for (MockMvcConfigurer configurer : this.configurers) { - configurer.beforeMockMvcCreated(this, this.defaultRequestBuilder, wac); + RequestPostProcessor processor = configurer.beforeMockMvcCreated(this, wac); + if (processor != null) { + if (this.defaultRequestBuilder == null) { + this.defaultRequestBuilder = MockMvcRequestBuilders.get("/"); + } + if (this.defaultRequestBuilder instanceof ConfigurableSmartRequestBuilder) { + ((ConfigurableSmartRequestBuilder) this.defaultRequestBuilder).with(processor); + } + } } Filter[] filterArray = this.filters.toArray(new Filter[this.filters.size()]); @@ -218,4 +153,4 @@ public abstract class AbstractMockMvcBuilder */ protected abstract WebApplicationContext initWebAppContext(); -} +} \ No newline at end of file diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/ConfigurableMockMvcBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/ConfigurableMockMvcBuilder.java new file mode 100644 index 00000000000..b44bd8cef95 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/ConfigurableMockMvcBuilder.java @@ -0,0 +1,127 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.web.servlet.setup; + +import org.springframework.test.web.servlet.MockMvcBuilder; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.ResultHandler; +import org.springframework.test.web.servlet.ResultMatcher; + +import javax.servlet.Filter; + +/** + * Defines common methods for building a {@code MockMvc}. + * + * @author Rossen Stoyanchev + * @since 4.1 + */ +public interface ConfigurableMockMvcBuilder> extends MockMvcBuilder { + + /** + * Add filters mapped to any request (i.e. "/*"). For example: + * + *

+	 * mockMvcBuilder.addFilters(springSecurityFilterChain);
+	 * 
+ * + *

is the equivalent of the following web.xml configuration: + * + *

+	 * <filter-mapping>
+	 *     <filter-name>springSecurityFilterChain</filter-name>
+	 *     <url-pattern>/*</url-pattern>
+	 * </filter-mapping>
+	 * 
+ * + *

Filters will be invoked in the order in which they are provided. + * + * @param filters the filters to add + */ + T addFilters(Filter... filters); + + /** + * Add a filter mapped to a specific set of patterns. For example: + * + *

+	 * mockMvcBuilder.addFilters(myResourceFilter, "/resources/*");
+	 * 
+ * + *

is the equivalent of: + * + *

+	 * <filter-mapping>
+	 *     <filter-name>myResourceFilter</filter-name>
+	 *     <url-pattern>/resources/*</url-pattern>
+	 * </filter-mapping>
+	 * 
+ * + *

Filters will be invoked in the order in which they are provided. + * + * @param filter the filter to add + * @param urlPatterns URL patterns to map to; if empty, "/*" is used by default + */ + T addFilter(Filter filter, String... urlPatterns); + + /** + * Define default request properties that should be merged into all + * performed requests. In effect this provides a mechanism for defining + * common initialization for all requests such as the content type, request + * parameters, session attributes, and any other request property. + * + *

Properties specified at the time of performing a request override the + * default properties defined here. + * + * @param requestBuilder a RequestBuilder; see static factory methods in + * {@link org.springframework.test.web.servlet.request.MockMvcRequestBuilders} + * . + */ + T defaultRequest(RequestBuilder requestBuilder); + + /** + * Define a global expectation that should always be applied to + * every response. For example, status code 200 (OK), content type + * {@code "application/json"}, etc. + * + * @param resultMatcher a ResultMatcher; see static factory methods in + * {@link org.springframework.test.web.servlet.result.MockMvcResultMatchers} + */ + T alwaysExpect(ResultMatcher resultMatcher); + + /** + * Define a global action that should always be applied to every + * response. For example, writing detailed information about the performed + * request and resulting response to {@code System.out}. + * + * @param resultHandler a ResultHandler; see static factory methods in + * {@link org.springframework.test.web.servlet.result.MockMvcResultHandlers} + */ + T alwaysDo(ResultHandler resultHandler); + + /** + * Whether to enable the DispatcherServlet property + * {@link org.springframework.web.servlet.DispatcherServlet#setDispatchOptionsRequest + * dispatchOptionsRequest} which allows processing of HTTP OPTIONS requests. + */ + T dispatchOptions(boolean dispatchOptions); + + /** + * Add a {@code MockMvcConfigurer} that automates MockMvc setup and + * configures it for some specific purpose (e.g. security). + */ + T apply(MockMvcConfigurer configurer); + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/MockMvcConfigurer.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/MockMvcConfigurer.java index 0ee733762e7..36394103431 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/MockMvcConfigurer.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/MockMvcConfigurer.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,45 +13,42 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.test.web.servlet.setup; -import org.springframework.test.web.servlet.MockMvcBuilder; -import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.web.context.WebApplicationContext; + /** - * A contract that allows the encapsulation of a "recipe" for configuring a - * MockMvcBuilder for some specific purpose. For example a 3rd party library - * may use this to provide convenient, easy ways to set up MockMvc. + * Allows a sub-class to encapsulate logic for pre-configuring a + * {@code ConfigurableMockMvcBuilder} for some specific purpose. A 3rd party + * library may use this to provide shortcuts for setting up MockMvc. * - *

Supported via {@link AbstractMockMvcBuilder#add(MockMvcConfigurer)} - * with instances of class likely created via static methods, e.g.: + *

Can be plugged in via {@link ConfigurableMockMvcBuilder#apply} with + * instances of this type likely created via static methods, e.g.: * *

- * 	MockMvcBuilders.webAppContextSetup(context)
- * 		.add(myLibrary("foo","bar").myProperty("foo"))
- * 		.build();
+ * 	MockMvcBuilders.webAppContextSetup(context).apply(mySetup("foo","bar")).build();
  * 
* * @author Rossen Stoyanchev * @since 4.1 + * @see org.springframework.test.web.servlet.setup.MockMvcConfigurerAdapter */ public interface MockMvcConfigurer { - /** - * Invoked immediately after a {@code MockMvcConfigurer} is configured via - * {@link AbstractMockMvcBuilder#add(MockMvcConfigurer)}. + * Invoked immediately after a {@code MockMvcConfigurer} is added via + * {@link ConfigurableMockMvcBuilder#apply}. */ - void afterConfigurerAdded(MockMvcBuilder mockMvcBuilder); + void afterConfigurerAdded(ConfigurableMockMvcBuilder builder); /** - * Invoked just before the MockMvc instance is built providing access to the - * configured "default" RequestBuilder. If a "default" RequestBuilder is - * needed but was not configured and is {@code null}), it can still be added - * via {@link AbstractMockMvcBuilder#defaultRequest}. + * Invoked just before the MockMvc instance is created. Implementations may + * return a RequestPostProcessor to be applied to every request performed + * through the created {@code MockMvc} instance. */ - void beforeMockMvcCreated(MockMvcBuilder mockMvcBuilder, RequestBuilder defaultRequestBuilder, - WebApplicationContext applicationContext); + RequestPostProcessor beforeMockMvcCreated(ConfigurableMockMvcBuilder builder, WebApplicationContext context); } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/MockMvcConfigurerAdapter.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/MockMvcConfigurerAdapter.java new file mode 100644 index 00000000000..3afe4384021 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/MockMvcConfigurerAdapter.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.web.servlet.setup; + +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.request.RequestPostProcessor; +import org.springframework.web.context.WebApplicationContext; + + +/** + * An empty method implementation of {@link MockMvcConfigurer}. + * + * @author Rossen Stoyanchev + * @since 4.1 + */ +public abstract class MockMvcConfigurerAdapter implements MockMvcConfigurer { + + @Override + public void afterConfigurerAdded(ConfigurableMockMvcBuilder builder) { + } + + @Override + public RequestPostProcessor beforeMockMvcCreated(ConfigurableMockMvcBuilder builder, WebApplicationContext cxt) { + return null; + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/RequestBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/FrameworkExtensionTests.java similarity index 56% rename from spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/RequestBuilderTests.java rename to spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/FrameworkExtensionTests.java index b36043a7e43..5ea48089acf 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/RequestBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/FrameworkExtensionTests.java @@ -15,6 +15,7 @@ */ package org.springframework.test.web.servlet.samples.standalone; +import static org.mockito.Mockito.mock; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -28,56 +29,72 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.stereotype.Controller; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.RequestPostProcessor; +import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder; +import org.springframework.test.web.servlet.setup.MockMvcConfigurerAdapter; +import org.springframework.util.Assert; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.context.WebApplicationContext; + +import java.security.Principal; /** - * Demonstrates how to implement and plug in a custom {@link RequestPostProcessor}. + * Demonstrates use of SPI extension points: + *
    + *
  • {@link org.springframework.test.web.servlet.request.RequestPostProcessor} + * for extending request building with custom methods. + *
  • {@link org.springframework.test.web.servlet.setup.MockMvcConfigurer + * MockMvcConfigurer} for extending MockMvc building with some automatic setup. + *
* * @author Rossen Stoyanchev */ -public class RequestBuilderTests { +public class FrameworkExtensionTests { private MockMvc mockMvc; + @Before public void setup() { - this.mockMvc = standaloneSetup(new SampleController()) - .defaultRequest(get("/").accept(MediaType.TEXT_PLAIN)) - .alwaysExpect(status().isOk()).build(); + this.mockMvc = standaloneSetup(new SampleController()).apply(defaultSetup()).build(); } + @Test public void fooHeader() throws Exception { - this.mockMvc.perform(get("/").with(headers().foo("a=b"))).andExpect( - content().string("Foo")); + this.mockMvc.perform(get("/").with(headers().foo("a=b"))).andExpect(content().string("Foo")); } @Test public void barHeader() throws Exception { - this.mockMvc.perform(get("/").with(headers().bar("a=b"))).andExpect( - content().string("Bar")); + this.mockMvc.perform(get("/").with(headers().bar("a=b"))).andExpect(content().string("Bar")); } - private static HeaderRequestPostProcessor headers() { - return new HeaderRequestPostProcessor(); + + private static TestMockMvcConfigurer defaultSetup() { + return new TestMockMvcConfigurer(); } + private static TestRequestPostProcessor headers() { + return new TestRequestPostProcessor(); + } + + /** - * Implementation of {@code RequestPostProcessor} with additional request - * building methods. + * Test {@code RequestPostProcessor}. */ - private static class HeaderRequestPostProcessor implements RequestPostProcessor { + private static class TestRequestPostProcessor implements RequestPostProcessor { private HttpHeaders headers = new HttpHeaders(); - public HeaderRequestPostProcessor foo(String value) { + + public TestRequestPostProcessor foo(String value) { this.headers.add("Foo", value); return this; } - public HeaderRequestPostProcessor bar(String value) { + public TestRequestPostProcessor bar(String value) { this.headers.add("Bar", value); return this; } @@ -91,19 +108,40 @@ public class RequestBuilderTests { } } + /** + * Test {@code MockMvcConfigurer}. + */ + private static class TestMockMvcConfigurer extends MockMvcConfigurerAdapter { + + @Override + public void afterConfigurerAdded(ConfigurableMockMvcBuilder builder) { + builder.alwaysExpect(status().isOk()); + } + + @Override + public RequestPostProcessor beforeMockMvcCreated(ConfigurableMockMvcBuilder builder, WebApplicationContext context) { + return request -> { + request.setUserPrincipal(mock(Principal.class)); + return request; + }; + } + } + @Controller @RequestMapping("/") private static class SampleController { @RequestMapping(headers = "Foo") @ResponseBody - public String handleFoo() { + public String handleFoo(Principal principal) { + Assert.isTrue(principal != null); return "Foo"; } @RequestMapping(headers = "Bar") @ResponseBody - public String handleBar() { + public String handleBar(Principal principal) { + Assert.isTrue(principal != null); return "Bar"; } }