Introduce AbstractMockMvcBuilder class

This change splits out an abstract base class from DefaultMockMvcBuilder
with StandaloneMockMvcBuilder switching to extend the new abstract class
(rather than DefaultMockMvcBuilder).

Issue: SPR-11238
This commit is contained in:
Rossen Stoyanchev 2013-12-23 14:02:48 -05:00
parent e3017c30bb
commit 2e4f38f6af
7 changed files with 253 additions and 188 deletions

View File

@ -42,7 +42,8 @@ public abstract class MockMvcBuilderSupport {
protected final MockMvc createMockMvc(Filter[] filters, MockServletConfig servletConfig,
WebApplicationContext webAppContext, RequestBuilder defaultRequestBuilder,
List<ResultMatcher> globalResultMatchers, List<ResultHandler> globalResultHandlers, Boolean dispatchOptions) {
List<ResultMatcher> globalResultMatchers, List<ResultHandler> globalResultHandlers,
Boolean dispatchOptions) {
ServletContext servletContext = webAppContext.getServletContext();

View File

@ -0,0 +1,203 @@
/*
* Copyright 2002-2013 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.mock.web.MockServletConfig;
import org.springframework.test.web.servlet.*;
import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
import javax.servlet.Filter;
import javax.servlet.ServletContext;
import java.util.ArrayList;
import java.util.List;
/**
* An abstract implementation of {@link org.springframework.test.web.servlet.MockMvcBuilder}
* with common methods for configuring filters, default request properties, global
* expectations and global result actions.
* <p>
* Sub-classes can use different strategies to prepare a WebApplicationContext to
* pass to the DispatcherServlet.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public abstract class AbstractMockMvcBuilder<B extends AbstractMockMvcBuilder<B>>
extends MockMvcBuilderSupport implements MockMvcBuilder {
private List<Filter> filters = new ArrayList<Filter>();
private RequestBuilder defaultRequestBuilder;
private final List<ResultMatcher> globalResultMatchers = new ArrayList<ResultMatcher>();
private final List<ResultHandler> globalResultHandlers = new ArrayList<ResultHandler>();
private Boolean dispatchOptions = Boolean.FALSE;
/**
* Add filters mapped to any request (i.e. "/*"). For example:
*
* <pre class="code">
* mockMvcBuilder.addFilters(springSecurityFilterChain);
* </pre>
*
* <p>is the equivalent of the following web.xml configuration:
*
* <pre class="code">
* &lt;filter-mapping&gt;
* &lt;filter-name&gt;springSecurityFilterChain&lt;/filter-name&gt;
* &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
* &lt;/filter-mapping&gt;
* </pre>
*
* <p>Filters will be invoked in the order in which they are provided.
*
* @param filters the filters to add
*/
@SuppressWarnings("unchecked")
public final <T extends B> T addFilters(Filter... filters) {
Assert.notNull(filters, "filters cannot be null");
for(Filter f : filters) {
Assert.notNull(f, "filters cannot contain null values");
this.filters.add(f);
}
return (T) this;
}
/**
* Add a filter mapped to a specific set of patterns. For example:
*
* <pre class="code">
* mockMvcBuilder.addFilters(myResourceFilter, "/resources/*");
* </pre>
*
* <p>is the equivalent of:
*
* <pre class="code">
* &lt;filter-mapping&gt;
* &lt;filter-name&gt;myResourceFilter&lt;/filter-name&gt;
* &lt;url-pattern&gt;/resources/*&lt;/url-pattern&gt;
* &lt;/filter-mapping&gt;
* </pre>
*
* <p>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 extends B> T addFilter(Filter filter, String... urlPatterns) {
Assert.notNull(filter, "filter cannot be null");
Assert.notNull(urlPatterns, "urlPatterns cannot be null");
if(urlPatterns.length > 0) {
filter = new PatternMappingFilterProxy(filter, urlPatterns);
}
this.filters.add(filter);
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.
*
* <p>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 extends B> T defaultRequest(RequestBuilder requestBuilder) {
this.defaultRequestBuilder = requestBuilder;
return (T) this;
}
/**
* Define a global expectation that should <em>always</em> 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 extends B> T alwaysExpect(ResultMatcher resultMatcher) {
this.globalResultMatchers.add(resultMatcher);
return (T) this;
}
/**
* Define a global action that should <em>always</em> 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 extends B> T alwaysDo(ResultHandler resultHandler) {
this.globalResultHandlers.add(resultHandler);
return (T) this;
}
/**
* Should the {@link org.springframework.web.servlet.DispatcherServlet} dispatch OPTIONS request to controllers.
* @param dispatchOptions
* @see org.springframework.web.servlet.DispatcherServlet#setDispatchOptionsRequest(boolean)
*/
@SuppressWarnings("unchecked")
public final <T extends B> T dispatchOptions(boolean dispatchOptions) {
this.dispatchOptions = dispatchOptions;
return (T) this;
}
/**
* Build a {@link org.springframework.test.web.servlet.MockMvc} instance.
*/
@Override
public final MockMvc build() {
WebApplicationContext wac = initWebAppContext();
ServletContext servletContext = wac.getServletContext();
MockServletConfig mockServletConfig = new MockServletConfig(servletContext);
Filter[] filterArray = this.filters.toArray(new Filter[this.filters.size()]);
return super.createMockMvc(filterArray, mockServletConfig, wac, this.defaultRequestBuilder,
this.globalResultMatchers, this.globalResultHandlers, this.dispatchOptions);
}
/**
* A method to obtain the WebApplicationContext to be passed to the DispatcherServlet.
* Invoked from {@link #build()} before the
* {@link org.springframework.test.web.servlet.MockMvc} instance is created.
*/
protected abstract WebApplicationContext initWebAppContext();
}

View File

@ -34,29 +34,17 @@ import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
/**
* An concrete implementation of {@link MockMvcBuilder} with methods for
* configuring filters, default request properties, and global expectations and
* result actions.
* An concrete implementation of {@link AbstractMockMvcBuilder} that simply
* provides the WebApplicationContext given to it as a constructor argument.
*
* @author Rossen Stoyanchev
* @author Rob Winch
* @since 3.2
*/
public class DefaultMockMvcBuilder<B extends DefaultMockMvcBuilder<B>> extends MockMvcBuilderSupport
implements MockMvcBuilder {
public class DefaultMockMvcBuilder extends AbstractMockMvcBuilder<DefaultMockMvcBuilder> {
private final WebApplicationContext webAppContext;
private List<Filter> filters = new ArrayList<Filter>();
private RequestBuilder defaultRequestBuilder;
private final List<ResultMatcher> globalResultMatchers = new ArrayList<ResultMatcher>();
private final List<ResultHandler> globalResultHandlers = new ArrayList<ResultHandler>();
private Boolean dispatchOptions = Boolean.FALSE;
/**
* Protected constructor. Not intended for direct instantiation.
@ -68,153 +56,10 @@ public class DefaultMockMvcBuilder<B extends DefaultMockMvcBuilder<B>> extends M
this.webAppContext = webAppContext;
}
/**
* Add filters mapped to any request (i.e. "/*"). For example:
*
* <pre class="code">
* mockMvcBuilder.addFilters(springSecurityFilterChain);
* </pre>
*
* <p>is the equivalent of the following web.xml configuration:
*
* <pre class="code">
* &lt;filter-mapping&gt;
* &lt;filter-name&gt;springSecurityFilterChain&lt;/filter-name&gt;
* &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
* &lt;/filter-mapping&gt;
* </pre>
*
* <p>Filters will be invoked in the order in which they are provided.
*
* @param filters the filters to add
*/
@SuppressWarnings("unchecked")
public final <T extends B> T addFilters(Filter... filters) {
Assert.notNull(filters, "filters cannot be null");
for(Filter f : filters) {
Assert.notNull(f, "filters cannot contain null values");
this.filters.add(f);
}
return (T) this;
}
/**
* Add a filter mapped to a specific set of patterns. For example:
*
* <pre class="code">
* mockMvcBuilder.addFilters(myResourceFilter, "/resources/*");
* </pre>
*
* <p>is the equivalent of:
*
* <pre class="code">
* &lt;filter-mapping&gt;
* &lt;filter-name&gt;myResourceFilter&lt;/filter-name&gt;
* &lt;url-pattern&gt;/resources/*&lt;/url-pattern&gt;
* &lt;/filter-mapping&gt;
* </pre>
*
* <p>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 extends B> T addFilter(Filter filter, String... urlPatterns) {
Assert.notNull(filter, "filter cannot be null");
Assert.notNull(urlPatterns, "urlPatterns cannot be null");
if(urlPatterns.length > 0) {
filter = new PatternMappingFilterProxy(filter, urlPatterns);
}
this.filters.add(filter);
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.
*
* <p>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 extends B> T defaultRequest(RequestBuilder requestBuilder) {
this.defaultRequestBuilder = requestBuilder;
return (T) this;
}
/**
* Define a global expectation that should <em>always</em> 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 extends B> T alwaysExpect(ResultMatcher resultMatcher) {
this.globalResultMatchers.add(resultMatcher);
return (T) this;
}
/**
* Define a global action that should <em>always</em> 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 extends B> T alwaysDo(ResultHandler resultHandler) {
this.globalResultHandlers.add(resultHandler);
return (T) this;
}
/**
* Should the {@link DispatcherServlet} dispatch OPTIONS request to controllers.
* @param dispatchOptions
* @see DispatcherServlet#setDispatchOptionsRequest(boolean)
*/
@SuppressWarnings("unchecked")
public final <T extends B> T dispatchOptions(boolean dispatchOptions) {
this.dispatchOptions = dispatchOptions;
return (T) this;
}
/**
* Build a {@link MockMvc} instance.
*/
@Override
public final MockMvc build() {
initWebAppContext(this.webAppContext);
ServletContext servletContext = this.webAppContext.getServletContext();
MockServletConfig mockServletConfig = new MockServletConfig(servletContext);
Filter[] filterArray = this.filters.toArray(new Filter[this.filters.size()]);
return super.createMockMvc(filterArray, mockServletConfig, this.webAppContext,
this.defaultRequestBuilder, this.globalResultMatchers, this.globalResultHandlers,this.dispatchOptions);
}
/**
* Invoked from {@link #build()} before the {@link MockMvc} instance is created.
* Allows sub-classes to further initialize the {@code WebApplicationContext}
* and the {@code javax.servlet.ServletContext} it contains.
*/
protected void initWebAppContext(WebApplicationContext webAppContext) {
protected WebApplicationContext initWebAppContext() {
return this.webAppContext;
}
}

View File

@ -42,8 +42,8 @@ public class MockMvcBuilders {
* application controllers in it. The context must have been configured with
* a {@link ServletContext}.
*/
public static <B extends DefaultMockMvcBuilder<B>> DefaultMockMvcBuilder<B> webAppContextSetup(WebApplicationContext context) {
return new DefaultMockMvcBuilder<B>(context);
public static DefaultMockMvcBuilder webAppContextSetup(WebApplicationContext context) {
return new DefaultMockMvcBuilder(context);
}
/**

View File

@ -82,7 +82,7 @@ import org.springframework.web.servlet.view.InternalResourceViewResolver;
* @author Rossen Stoyanchev
* @since 3.2
*/
public class StandaloneMockMvcBuilder extends DefaultMockMvcBuilder<StandaloneMockMvcBuilder> {
public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneMockMvcBuilder> {
private final Object[] controllers;
@ -124,7 +124,6 @@ public class StandaloneMockMvcBuilder extends DefaultMockMvcBuilder<StandaloneMo
* @see MockMvcBuilders#standaloneSetup(Object...)
*/
protected StandaloneMockMvcBuilder(Object... controllers) {
super(new StubWebApplicationContext(new MockServletContext()));
Assert.isTrue(!ObjectUtils.isEmpty(controllers), "At least one controller is required");
this.controllers = controllers;
}
@ -308,37 +307,39 @@ public class StandaloneMockMvcBuilder extends DefaultMockMvcBuilder<StandaloneMo
@Override
protected void initWebAppContext(WebApplicationContext cxt) {
StubWebApplicationContext mockCxt = (StubWebApplicationContext) cxt;
registerMvcSingletons(mockCxt);
cxt.getServletContext().setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, mockCxt);
protected WebApplicationContext initWebAppContext() {
MockServletContext servletContext = new MockServletContext();
StubWebApplicationContext wac = new StubWebApplicationContext(servletContext);
registerMvcSingletons(wac);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac);
return wac;
}
private void registerMvcSingletons(StubWebApplicationContext cxt) {
private void registerMvcSingletons(StubWebApplicationContext wac) {
StandaloneConfiguration config = new StandaloneConfiguration();
StaticRequestMappingHandlerMapping hm = config.getHandlerMapping();
hm.setServletContext(cxt.getServletContext());
hm.setApplicationContext(cxt);
hm.registerHandlers(controllers);
cxt.addBean("requestMappingHandlerMapping", hm);
hm.setServletContext(wac.getServletContext());
hm.setApplicationContext(wac);
hm.registerHandlers(this.controllers);
wac.addBean("requestMappingHandlerMapping", hm);
RequestMappingHandlerAdapter handlerAdapter = config.requestMappingHandlerAdapter();
handlerAdapter.setServletContext(cxt.getServletContext());
handlerAdapter.setApplicationContext(cxt);
handlerAdapter.setServletContext(wac.getServletContext());
handlerAdapter.setApplicationContext(wac);
handlerAdapter.afterPropertiesSet();
cxt.addBean("requestMappingHandlerAdapter", handlerAdapter);
wac.addBean("requestMappingHandlerAdapter", handlerAdapter);
cxt.addBean("handlerExceptionResolver", config.handlerExceptionResolver());
wac.addBean("handlerExceptionResolver", config.handlerExceptionResolver());
cxt.addBeans(initViewResolvers(cxt));
cxt.addBean(DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME, this.localeResolver);
cxt.addBean(DispatcherServlet.THEME_RESOLVER_BEAN_NAME, new FixedThemeResolver());
cxt.addBean(DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, new DefaultRequestToViewNameTranslator());
wac.addBeans(initViewResolvers(wac));
wac.addBean(DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME, this.localeResolver);
wac.addBean(DispatcherServlet.THEME_RESOLVER_BEAN_NAME, new FixedThemeResolver());
wac.addBean(DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, new DefaultRequestToViewNameTranslator());
this.flashMapManager = new SessionFlashMapManager();
cxt.addBean(DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME, this.flashMapManager);
wac.addBean(DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME, this.flashMapManager);
}
private List<ViewResolver> initViewResolvers(WebApplicationContext wac) {

View File

@ -35,7 +35,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
*/
public class DefaultMockMvcBuilderTests {
private DefaultMockMvcBuilder<?> builder;
private StandaloneMockMvcBuilder builder;
@Before
public void setup() {

View File

@ -21,6 +21,7 @@ import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockServletContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
@ -38,12 +39,11 @@ public class StandaloneMockMvcBuilderTests {
@Test
public void placeHoldersInRequestMapping() throws Exception {
StubWebApplicationContext cxt = new StubWebApplicationContext(new MockServletContext());
StandaloneMockMvcBuilder builder = new StandaloneMockMvcBuilder(new PlaceholderController());
TestStandaloneMockMvcBuilder builder = new TestStandaloneMockMvcBuilder(new PlaceholderController());
builder.addPlaceHolderValue("sys.login.ajax", "/foo");
builder.initWebAppContext(cxt);
builder.build();
RequestMappingHandlerMapping hm = cxt.getBean(RequestMappingHandlerMapping.class);
RequestMappingHandlerMapping hm = builder.wac.getBean(RequestMappingHandlerMapping.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
HandlerExecutionChain chain = hm.getHandler(request);
@ -60,4 +60,19 @@ public class StandaloneMockMvcBuilderTests {
private void handleWithPlaceholders() { }
}
private static class TestStandaloneMockMvcBuilder extends StandaloneMockMvcBuilder {
private WebApplicationContext wac;
private TestStandaloneMockMvcBuilder(Object... controllers) {
super(controllers);
}
@Override
protected WebApplicationContext initWebAppContext() {
this.wac = super.initWebAppContext();
return this.wac;
}
}
}