Add MockMvcTestClient

See gh-19647
This commit is contained in:
Rossen Stoyanchev
2020-08-19 21:12:04 +01:00
parent 128acaff8a
commit 3426e6274c
43 changed files with 5299 additions and 58 deletions

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2002-2020 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
*
* https://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.client;
import javax.servlet.Filter;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.DispatcherServletCustomizer;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
/**
* Base class for implementations of {@link MockMvcTestClient.MockMvcServerSpec}
* that simply delegates to a {@link ConfigurableMockMvcBuilder} supplied by
* the concrete sub-classes.
*
* @author Rossen Stoyanchev
* @since 5.3
* @param <B> the type of the concrete sub-class spec
*/
abstract class AbstractMockMvcServerSpec<B extends MockMvcTestClient.MockMvcServerSpec<B>>
implements MockMvcTestClient.MockMvcServerSpec<B> {
@Override
public <T extends B> T filters(Filter... filters) {
getMockMvcBuilder().addFilters(filters);
return self();
}
public final <T extends B> T filter(Filter filter, String... urlPatterns) {
getMockMvcBuilder().addFilter(filter, urlPatterns);
return self();
}
@Override
public <T extends B> T defaultRequest(RequestBuilder requestBuilder) {
getMockMvcBuilder().defaultRequest(requestBuilder);
return self();
}
@Override
public <T extends B> T alwaysExpect(ResultMatcher resultMatcher) {
getMockMvcBuilder().alwaysExpect(resultMatcher);
return self();
}
@Override
public <T extends B> T dispatchOptions(boolean dispatchOptions) {
getMockMvcBuilder().dispatchOptions(dispatchOptions);
return self();
}
@Override
public <T extends B> T dispatcherServletCustomizer(DispatcherServletCustomizer customizer) {
getMockMvcBuilder().addDispatcherServletCustomizer(customizer);
return self();
}
@Override
public <T extends B> T apply(MockMvcConfigurer configurer) {
getMockMvcBuilder().apply(configurer);
return self();
}
@SuppressWarnings("unchecked")
private <T extends B> T self() {
return (T) this;
}
/**
* Return the concrete {@link ConfigurableMockMvcBuilder} to delegate
* configuration methods and to use to create the {@link MockMvc}.
*/
protected abstract ConfigurableMockMvcBuilder<?> getMockMvcBuilder();
@Override
public WebTestClient.Builder configureClient() {
MockMvc mockMvc = getMockMvcBuilder().build();
ClientHttpConnector connector = new MockMvcHttpConnector(mockMvc);
return WebTestClient.bindToServer(connector);
}
@Override
public WebTestClient build() {
return configureClient().build();
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2002-2020 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
*
* https://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.client;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
/**
* Simple wrapper around a {@link DefaultMockMvcBuilder}.
*
* @author Rossen Stoyanchev
* @since 5.3
*/
class ApplicationContextMockMvcSpec extends AbstractMockMvcServerSpec<ApplicationContextMockMvcSpec> {
private final DefaultMockMvcBuilder mockMvcBuilder;
public ApplicationContextMockMvcSpec(WebApplicationContext context) {
this.mockMvcBuilder = MockMvcBuilders.webAppContextSetup(context);
}
@Override
protected ConfigurableMockMvcBuilder<?> getMockMvcBuilder() {
return this.mockMvcBuilder;
}
}

View File

@@ -0,0 +1,301 @@
/*
* Copyright 2002-2020 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
*
* https://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.client;
import java.io.StringWriter;
import java.net.URI;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import javax.servlet.http.Cookie;
import reactor.core.publisher.Mono;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.http.ResponseCookie;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.http.codec.multipart.DefaultPartHttpMessageReader;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.codec.multipart.Part;
import org.springframework.lang.Nullable;
import org.springframework.mock.http.client.reactive.MockClientHttpRequest;
import org.springframework.mock.http.client.reactive.MockClientHttpResponse;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockPart;
import org.springframework.test.web.reactive.server.MockServerClientHttpResponse;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
/**
* Connector that handles requests by invoking a {@link MockMvc} rather than
* making actual requests over HTTP.
*
* @author Rossen Stoyanchev
* @since 5.3
*/
public class MockMvcHttpConnector implements ClientHttpConnector {
private static final DefaultPartHttpMessageReader MULTIPART_READER = new DefaultPartHttpMessageReader();
private static final Duration TIMEOUT = Duration.ofSeconds(5);
private final MockMvc mockMvc;
public MockMvcHttpConnector(MockMvc mockMvc) {
this.mockMvc = mockMvc;
}
@Override
public Mono<ClientHttpResponse> connect(
HttpMethod method, URI uri, Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {
RequestBuilder requestBuilder = adaptRequest(method, uri, requestCallback);
try {
MvcResult mvcResult = this.mockMvc.perform(requestBuilder).andReturn();
if (mvcResult.getRequest().isAsyncStarted()) {
mvcResult.getAsyncResult();
mvcResult = this.mockMvc.perform(asyncDispatch(mvcResult)).andReturn();
}
return Mono.just(adaptResponse(mvcResult));
}
catch (Exception ex) {
return Mono.error(ex);
}
}
private RequestBuilder adaptRequest(
HttpMethod httpMethod, URI uri, Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {
MockClientHttpRequest httpRequest = new MockClientHttpRequest(httpMethod, uri);
AtomicReference<byte[]> contentRef = new AtomicReference<>();
httpRequest.setWriteHandler(dataBuffers ->
DataBufferUtils.join(dataBuffers)
.doOnNext(buffer -> {
byte[] bytes = new byte[buffer.readableByteCount()];
buffer.read(bytes);
DataBufferUtils.release(buffer);
contentRef.set(bytes);
})
.then());
// Initialize the client request
requestCallback.apply(httpRequest).block(TIMEOUT);
MockHttpServletRequestBuilder requestBuilder =
initRequestBuilder(httpMethod, uri, httpRequest, contentRef.get());
requestBuilder.headers(httpRequest.getHeaders());
for (List<HttpCookie> cookies : httpRequest.getCookies().values()) {
for (HttpCookie cookie : cookies) {
requestBuilder.cookie(new Cookie(cookie.getName(), cookie.getValue()));
}
}
return requestBuilder;
}
private MockHttpServletRequestBuilder initRequestBuilder(
HttpMethod httpMethod, URI uri, MockClientHttpRequest httpRequest, @Nullable byte[] bytes) {
String contentType = httpRequest.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
if (!StringUtils.startsWithIgnoreCase(contentType, "multipart/")) {
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.request(httpMethod, uri);
if (!ObjectUtils.isEmpty(bytes)) {
requestBuilder.content(bytes);
}
return requestBuilder;
}
// Parse the multipart request in order to adapt to Servlet Part's
MockMultipartHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.multipart(uri);
Assert.notNull(bytes, "No multipart content");
ReactiveHttpInputMessage inputMessage = MockServerHttpRequest.post(uri.toString())
.headers(httpRequest.getHeaders())
.body(Mono.just(DefaultDataBufferFactory.sharedInstance.wrap(bytes)));
MULTIPART_READER.read(ResolvableType.forClass(Part.class), inputMessage, Collections.emptyMap())
.flatMap(part ->
DataBufferUtils.join(part.content())
.doOnNext(buffer -> {
byte[] partBytes = new byte[buffer.readableByteCount()];
buffer.read(partBytes);
DataBufferUtils.release(buffer);
// Adapt to javax.servlet.http.Part...
MockPart mockPart = (part instanceof FilePart ?
new MockPart(part.name(), ((FilePart) part).filename(), partBytes) :
new MockPart(part.name(), partBytes));
mockPart.getHeaders().putAll(part.headers());
requestBuilder.part(mockPart);
}))
.blockLast(TIMEOUT);
return requestBuilder;
}
private MockClientHttpResponse adaptResponse(MvcResult mvcResult) {
MockClientHttpResponse clientResponse = new MockMvcServerClientHttpResponse(mvcResult);
MockHttpServletResponse servletResponse = mvcResult.getResponse();
for (String header : servletResponse.getHeaderNames()) {
for (String value : servletResponse.getHeaders(header)) {
clientResponse.getHeaders().add(header, value);
}
}
if (servletResponse.getForwardedUrl() != null) {
clientResponse.getHeaders().add("Forwarded-Url", servletResponse.getForwardedUrl());
}
for (Cookie cookie : servletResponse.getCookies()) {
ResponseCookie httpCookie =
ResponseCookie.fromClientResponse(cookie.getName(), cookie.getValue())
.maxAge(Duration.ofSeconds(cookie.getMaxAge()))
.domain(cookie.getDomain())
.path(cookie.getPath())
.secure(cookie.getSecure())
.httpOnly(cookie.isHttpOnly())
.build();
clientResponse.getCookies().add(httpCookie.getName(), httpCookie);
}
byte[] bytes = servletResponse.getContentAsByteArray();
DefaultDataBuffer dataBuffer = DefaultDataBufferFactory.sharedInstance.wrap(bytes);
clientResponse.setBody(Mono.just(dataBuffer));
return clientResponse;
}
private static class MockMvcServerClientHttpResponse
extends MockClientHttpResponse implements MockServerClientHttpResponse {
private final MvcResult mvcResult;
public MockMvcServerClientHttpResponse(MvcResult result) {
super(result.getResponse().getStatus());
this.mvcResult = new PrintingMvcResult(result);
}
@Override
public Object getServerResult() {
return this.mvcResult;
}
}
private static class PrintingMvcResult implements MvcResult {
private final MvcResult mvcResult;
public PrintingMvcResult(MvcResult mvcResult) {
this.mvcResult = mvcResult;
}
@Override
public MockHttpServletRequest getRequest() {
return this.mvcResult.getRequest();
}
@Override
public MockHttpServletResponse getResponse() {
return this.mvcResult.getResponse();
}
@Nullable
@Override
public Object getHandler() {
return this.mvcResult.getHandler();
}
@Nullable
@Override
public HandlerInterceptor[] getInterceptors() {
return this.mvcResult.getInterceptors();
}
@Nullable
@Override
public ModelAndView getModelAndView() {
return this.mvcResult.getModelAndView();
}
@Nullable
@Override
public Exception getResolvedException() {
return this.mvcResult.getResolvedException();
}
@Override
public FlashMap getFlashMap() {
return this.mvcResult.getFlashMap();
}
@Override
public Object getAsyncResult() {
return this.mvcResult.getAsyncResult();
}
@Override
public Object getAsyncResult(long timeToWait) {
return this.mvcResult.getAsyncResult(timeToWait);
}
@Override
public String toString() {
StringWriter writer = new StringWriter();
try {
MockMvcResultHandlers.print(writer).handle(this);
}
catch (Exception ex) {
writer.append("Unable to format ")
.append(String.valueOf(this))
.append(": ")
.append(ex.getMessage());
}
return writer.toString();
}
}
}

View File

@@ -0,0 +1,380 @@
/*
* Copyright 2002-2020 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
*
* https://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.client;
import java.util.function.Supplier;
import javax.servlet.Filter;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.test.web.reactive.server.ExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.DispatcherServletCustomizer;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder;
import org.springframework.util.Assert;
import org.springframework.validation.Validator;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.FlashMapManager;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* The main class for testing Spring MVC applications via {@link WebTestClient}
* with {@link MockMvc} for server request handling.
*
* <p>Provides static factory methods and specs to initialize {@code MockMvc}
* to which the {@code WebTestClient} connects to. For example:
* <pre class="code">
* WebTestClient client = MockMvcTestClient.bindToController(myController)
* .controllerAdvice(myControllerAdvice)
* .validator(myValidator)
* .build()
* </pre>
*
* <p>The client itself can also be configured. For example:
* <pre class="code">
* WebTestClient client = MockMvcTestClient.bindToController(myController)
* .validator(myValidator)
* .configureClient()
* .baseUrl("/path")
* .build();
* </pre>
*
* @author Rossen Stoyanchev
* @since 5.3
*/
public interface MockMvcTestClient {
/**
* Begin creating a {@link WebTestClient} by providing the {@code @Controller}
* instance(s) to handle requests with.
* <p>Internally this is delegated to and equivalent to using
* {@link org.springframework.test.web.servlet.setup.MockMvcBuilders#standaloneSetup(Object...)}.
* to initialize {@link MockMvc}.
*/
static ControllerSpec bindToController(Object... controllers) {
return new StandaloneMockMvcSpec(controllers);
}
/**
* Begin creating a {@link WebTestClient} by providing a
* {@link WebApplicationContext} with Spring MVC infrastructure and
* controllers.
* <p>Internally this is delegated to and equivalent to using
* {@link org.springframework.test.web.servlet.setup.MockMvcBuilders#webAppContextSetup(WebApplicationContext)}
* to initialize {@code MockMvc}.
*/
static MockMvcServerSpec<?> bindToApplicationContext(WebApplicationContext context) {
return new ApplicationContextMockMvcSpec(context);
}
/**
* Begin creating a {@link WebTestClient} by providing an already
* initialized {@link MockMvc} instance to use as the server.
*/
static WebTestClient.Builder bindTo(MockMvc mockMvc) {
ClientHttpConnector connector = new MockMvcHttpConnector(mockMvc);
return WebTestClient.bindToServer(connector);
}
/**
* This method can be used to apply further assertions on a given
* {@link ExchangeResult} based the state of the server response.
*
* <p>Normally {@link WebTestClient} is used to assert the client response
* including HTTP status, headers, and body. That is all that is available
* when making a live request over HTTP. However when the server is
* {@link MockMvc}, many more assertions are possible against the server
* response, e.g. model attributes, flash attributes, etc.
*
* <p>Example:
* <pre class="code">
* EntityExchangeResult&lt;Void&gt; result =
* webTestClient.post().uri("/people/123")
* .exchange()
* .expectStatus().isFound()
* .expectHeader().location("/persons/Joe")
* .expectBody().isEmpty();
*
* MockMvcTestClient.resultActionsFor(result)
* .andExpect(model().size(1))
* .andExpect(model().attributeExists("name"))
* .andExpect(flash().attributeCount(1))
* .andExpect(flash().attribute("message", "success!"));
* </pre>
*
* <p>Note: this method works only if the {@link WebTestClient} used to
* perform the request was initialized through one of bind method in this
* class, and therefore requests are handled by {@link MockMvc}.
*/
static ResultActions resultActionsFor(ExchangeResult exchangeResult) {
Object serverResult = exchangeResult.getMockServerResult();
Assert.notNull(serverResult, "No MvcResult");
Assert.isInstanceOf(MvcResult.class, serverResult);
return new ResultActions() {
@Override
public ResultActions andExpect(ResultMatcher matcher) throws Exception {
matcher.match((MvcResult) serverResult);
return this;
}
@Override
public ResultActions andDo(ResultHandler handler) throws Exception {
handler.handle((MvcResult) serverResult);
return this;
}
@Override
public MvcResult andReturn() {
return (MvcResult) serverResult;
}
};
}
/**
* Base specification for configuring {@link MockMvc}, and a simple facade
* around {@link ConfigurableMockMvcBuilder}.
*
* @param <B> a self reference to the builder type
*/
interface MockMvcServerSpec<B extends MockMvcServerSpec<B>> {
/**
* Add a global filter.
* <p>This is delegated to
* {@link ConfigurableMockMvcBuilder#addFilters(Filter...)}.
*/
<T extends B> T filters(Filter... filters);
/**
* Add a filter for specific URL patterns.
* <p>This is delegated to
* {@link ConfigurableMockMvcBuilder#addFilter(Filter, String...)}.
*/
<T extends B> T filter(Filter filter, String... urlPatterns);
/**
* Define default request properties that should be merged into all
* performed requests such that input from the client request override
* the default properties defined here.
* <p>This is delegated to
* {@link ConfigurableMockMvcBuilder#defaultRequest(RequestBuilder)}.
*/
<T extends B> T defaultRequest(RequestBuilder requestBuilder);
/**
* Define a global expectation that should <em>always</em> be applied to
* every response.
* <p>This is delegated to
* {@link ConfigurableMockMvcBuilder#alwaysExpect(ResultMatcher)}.
*/
<T extends B> T alwaysExpect(ResultMatcher resultMatcher);
/**
* Whether to handle HTTP OPTIONS requests.
* <p>This is delegated to
* {@link ConfigurableMockMvcBuilder#dispatchOptions(boolean)}.
*/
<T extends B> T dispatchOptions(boolean dispatchOptions);
/**
* Allow customization of {@code DispatcherServlet}.
* <p>This is delegated to
* {@link ConfigurableMockMvcBuilder#addDispatcherServletCustomizer(DispatcherServletCustomizer)}.
*/
<T extends B> T dispatcherServletCustomizer(DispatcherServletCustomizer customizer);
/**
* Add a {@code MockMvcConfigurer} that automates MockMvc setup.
* <p>This is delegated to
* {@link ConfigurableMockMvcBuilder#apply(MockMvcConfigurer)}.
*/
<T extends B> T apply(MockMvcConfigurer configurer);
/**
* Proceed to configure and build the test client.
*/
WebTestClient.Builder configureClient();
/**
* Shortcut to build the test client.
*/
WebTestClient build();
}
/**
* Specification for configuring {@link MockMvc} to test one or more
* controllers directly, and a simple facade around
* {@link StandaloneMockMvcBuilder}.
*/
interface ControllerSpec extends MockMvcServerSpec<ControllerSpec> {
/**
* Register {@link org.springframework.web.bind.annotation.ControllerAdvice}
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#setControllerAdvice(Object...)}.
*/
ControllerSpec controllerAdvice(Object... controllerAdvice);
/**
* Set the message converters to use.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#setMessageConverters(HttpMessageConverter[])}.
*/
ControllerSpec messageConverters(HttpMessageConverter<?>... messageConverters);
/**
* Provide a custom {@link Validator}.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#setValidator(Validator)}.
*/
ControllerSpec validator(Validator validator);
/**
* Provide a conversion service.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#setConversionService(FormattingConversionService)}.
*/
ControllerSpec conversionService(FormattingConversionService conversionService);
/**
* Add global interceptors.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#addInterceptors(HandlerInterceptor...)}.
*/
ControllerSpec interceptors(HandlerInterceptor... interceptors);
/**
* Add interceptors for specific patterns.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#addMappedInterceptors(String[], HandlerInterceptor...)}.
*/
ControllerSpec mappedInterceptors(
@Nullable String[] pathPatterns, HandlerInterceptor... interceptors);
/**
* Set a ContentNegotiationManager.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#setContentNegotiationManager(ContentNegotiationManager)}.
*/
ControllerSpec contentNegotiationManager(ContentNegotiationManager manager);
/**
* Specify the timeout value for async execution.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#setAsyncRequestTimeout(long)}.
*/
ControllerSpec asyncRequestTimeout(long timeout);
/**
* Provide custom argument resolvers.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#setCustomArgumentResolvers(HandlerMethodArgumentResolver...)}.
*/
ControllerSpec customArgumentResolvers(HandlerMethodArgumentResolver... argumentResolvers);
/**
* Provide custom return value handlers.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#setCustomReturnValueHandlers(HandlerMethodReturnValueHandler...)}.
*/
ControllerSpec customReturnValueHandlers(HandlerMethodReturnValueHandler... handlers);
/**
* Set the HandlerExceptionResolver types to use.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#setHandlerExceptionResolvers(HandlerExceptionResolver...)}.
*/
ControllerSpec handlerExceptionResolvers(HandlerExceptionResolver... exceptionResolvers);
/**
* Set up view resolution.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#setViewResolvers(ViewResolver...)}.
*/
ControllerSpec viewResolvers(ViewResolver... resolvers);
/**
* Set up a single {@link ViewResolver} with a fixed view.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#setSingleView(View)}.
*/
ControllerSpec singleView(View view);
/**
* Provide the LocaleResolver to use.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#setLocaleResolver(LocaleResolver)}.
*/
ControllerSpec localeResolver(LocaleResolver localeResolver);
/**
* Provide a custom FlashMapManager.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#setFlashMapManager(FlashMapManager)}.
*/
ControllerSpec flashMapManager(FlashMapManager flashMapManager);
/**
* Enable URL path matching with parsed
* {@link org.springframework.web.util.pattern.PathPattern PathPatterns}.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#setPatternParser(PathPatternParser)}.
*/
ControllerSpec patternParser(PathPatternParser parser);
/**
* Whether to match trailing slashes.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#setUseTrailingSlashPatternMatch(boolean)}.
*/
ControllerSpec useTrailingSlashPatternMatch(boolean useTrailingSlashPatternMatch);
/**
* Configure placeholder values to use.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#addPlaceholderValue(String, String)}.
*/
ControllerSpec placeholderValue(String name, String value);
/**
* Configure factory for a custom {@link RequestMappingHandlerMapping}.
* <p>This is delegated to
* {@link StandaloneMockMvcBuilder#setCustomHandlerMapping(Supplier)}.
*/
ControllerSpec customHandlerMapping(Supplier<RequestMappingHandlerMapping> factory);
}
}

View File

@@ -0,0 +1,176 @@
/*
* Copyright 2002-2020 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
*
* https://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.client;
import java.util.function.Supplier;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder;
import org.springframework.validation.Validator;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.FlashMapManager;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* Simple wrapper around a {@link StandaloneMockMvcBuilder} that implements
* {@link MockMvcTestClient.ControllerSpec}.
*
* @author Rossen Stoyanchev
* @since 5.3
*/
class StandaloneMockMvcSpec extends AbstractMockMvcServerSpec<MockMvcTestClient.ControllerSpec>
implements MockMvcTestClient.ControllerSpec {
private final StandaloneMockMvcBuilder mockMvcBuilder;
StandaloneMockMvcSpec(Object... controllers) {
this.mockMvcBuilder = MockMvcBuilders.standaloneSetup(controllers);
}
@Override
public StandaloneMockMvcSpec controllerAdvice(Object... controllerAdvice) {
this.mockMvcBuilder.setControllerAdvice(controllerAdvice);
return this;
}
@Override
public StandaloneMockMvcSpec messageConverters(HttpMessageConverter<?>... messageConverters) {
this.mockMvcBuilder.setMessageConverters(messageConverters);
return this;
}
@Override
public StandaloneMockMvcSpec validator(Validator validator) {
this.mockMvcBuilder.setValidator(validator);
return this;
}
@Override
public StandaloneMockMvcSpec conversionService(FormattingConversionService conversionService) {
this.mockMvcBuilder.setConversionService(conversionService);
return this;
}
@Override
public StandaloneMockMvcSpec interceptors(HandlerInterceptor... interceptors) {
mappedInterceptors(null, interceptors);
return this;
}
@Override
public StandaloneMockMvcSpec mappedInterceptors(
@Nullable String[] pathPatterns, HandlerInterceptor... interceptors) {
this.mockMvcBuilder.addMappedInterceptors(pathPatterns, interceptors);
return this;
}
@Override
public StandaloneMockMvcSpec contentNegotiationManager(ContentNegotiationManager manager) {
this.mockMvcBuilder.setContentNegotiationManager(manager);
return this;
}
@Override
public StandaloneMockMvcSpec asyncRequestTimeout(long timeout) {
this.mockMvcBuilder.setAsyncRequestTimeout(timeout);
return this;
}
@Override
public StandaloneMockMvcSpec customArgumentResolvers(HandlerMethodArgumentResolver... argumentResolvers) {
this.mockMvcBuilder.setCustomArgumentResolvers(argumentResolvers);
return this;
}
@Override
public StandaloneMockMvcSpec customReturnValueHandlers(HandlerMethodReturnValueHandler... handlers) {
this.mockMvcBuilder.setCustomReturnValueHandlers(handlers);
return this;
}
@Override
public StandaloneMockMvcSpec handlerExceptionResolvers(HandlerExceptionResolver... exceptionResolvers) {
this.mockMvcBuilder.setHandlerExceptionResolvers(exceptionResolvers);
return this;
}
@Override
public StandaloneMockMvcSpec viewResolvers(ViewResolver... resolvers) {
this.mockMvcBuilder.setViewResolvers(resolvers);
return this;
}
@Override
public StandaloneMockMvcSpec singleView(View view) {
this.mockMvcBuilder.setSingleView(view);
return this;
}
@Override
public StandaloneMockMvcSpec localeResolver(LocaleResolver localeResolver) {
this.mockMvcBuilder.setLocaleResolver(localeResolver);
return this;
}
@Override
public StandaloneMockMvcSpec flashMapManager(FlashMapManager flashMapManager) {
this.mockMvcBuilder.setFlashMapManager(flashMapManager);
return this;
}
@Override
public StandaloneMockMvcSpec patternParser(PathPatternParser parser) {
this.mockMvcBuilder.setPatternParser(parser);
return this;
}
@Override
public StandaloneMockMvcSpec useTrailingSlashPatternMatch(boolean useTrailingSlashPatternMatch) {
this.mockMvcBuilder.setUseTrailingSlashPatternMatch(useTrailingSlashPatternMatch);
return this;
}
@Override
public StandaloneMockMvcSpec placeholderValue(String name, String value) {
this.mockMvcBuilder.addPlaceholderValue(name, value);
return this;
}
@Override
public StandaloneMockMvcSpec customHandlerMapping(Supplier<RequestMappingHandlerMapping> factory) {
this.mockMvcBuilder.setCustomHandlerMapping(factory);
return this;
}
@Override
public ConfigurableMockMvcBuilder<?> getMockMvcBuilder() {
return this.mockMvcBuilder;
}
}

View File

@@ -0,0 +1,13 @@
/**
* Support for testing Spring MVC applications via
* {@link org.springframework.test.web.reactive.server.WebTestClient}
* with {@link org.springframework.test.web.servlet.MockMvc} for server request
* handling.
*/
@NonNullApi
@NonNullFields
package org.springframework.test.web.servlet.client;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;