Add MockMvcTestClient
See gh-19647
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Void> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user