Merge branch 'webtestclient-experiment'

This commit is contained in:
Rossen Stoyanchev 2020-08-19 21:16:04 +01:00
commit ccb719eae3
57 changed files with 5856 additions and 118 deletions

View File

@ -80,7 +80,7 @@ public class MockClientHttpResponse implements ClientHttpResponse {
public HttpHeaders getHeaders() {
if (!getCookies().isEmpty() && this.headers.get(HttpHeaders.SET_COOKIE) == null) {
getCookies().values().stream().flatMap(Collection::stream)
.forEach(cookie -> getHeaders().add(HttpHeaders.SET_COOKIE, cookie.toString()));
.forEach(cookie -> this.headers.add(HttpHeaders.SET_COOKIE, cookie.toString()));
}
return this.headers;
}

View File

@ -0,0 +1,220 @@
/*
* 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.reactive.server;
import java.time.Duration;
import java.util.function.Consumer;
import org.hamcrest.Matcher;
import org.hamcrest.MatcherAssert;
import org.springframework.http.ResponseCookie;
import org.springframework.test.util.AssertionErrors;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* Assertions on cookies of the response.
* @author Rossen Stoyanchev
*/
public class CookieAssertions {
private final ExchangeResult exchangeResult;
private final WebTestClient.ResponseSpec responseSpec;
public CookieAssertions(ExchangeResult exchangeResult, WebTestClient.ResponseSpec responseSpec) {
this.exchangeResult = exchangeResult;
this.responseSpec = responseSpec;
}
/**
* Expect a header with the given name to match the specified values.
*/
public WebTestClient.ResponseSpec valueEquals(String name, String value) {
this.exchangeResult.assertWithDiagnostics(() -> {
String message = getMessage(name);
AssertionErrors.assertEquals(message, value, getCookie(name).getValue());
});
return this.responseSpec;
}
/**
* Assert the first value of the response cookie with a Hamcrest {@link Matcher}.
*/
public WebTestClient.ResponseSpec value(String name, Matcher<? super String> matcher) {
String value = getCookie(name).getValue();
this.exchangeResult.assertWithDiagnostics(() -> {
String message = getMessage(name);
MatcherAssert.assertThat(message, value, matcher);
});
return this.responseSpec;
}
/**
* Consume the value of the response cookie.
*/
public WebTestClient.ResponseSpec value(String name, Consumer<String> consumer) {
String value = getCookie(name).getValue();
this.exchangeResult.assertWithDiagnostics(() -> consumer.accept(value));
return this.responseSpec;
}
/**
* Expect that the cookie with the given name is present.
*/
public WebTestClient.ResponseSpec exists(String name) {
getCookie(name);
return this.responseSpec;
}
/**
* Expect that the cookie with the given name is not present.
*/
public WebTestClient.ResponseSpec doesNotExist(String name) {
ResponseCookie cookie = this.exchangeResult.getResponseCookies().getFirst(name);
if (cookie != null) {
String message = getMessage(name) + " exists with value=[" + cookie.getValue() + "]";
this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.fail(message));
}
return this.responseSpec;
}
/**
* Assert a cookie's maxAge attribute.
*/
public WebTestClient.ResponseSpec maxAge(String name, Duration expected) {
Duration maxAge = getCookie(name).getMaxAge();
this.exchangeResult.assertWithDiagnostics(() -> {
String message = getMessage(name) + " maxAge";
AssertionErrors.assertEquals(message, expected, maxAge);
});
return this.responseSpec;
}
/**
* Assert a cookie's maxAge attribute with a Hamcrest {@link Matcher}.
*/
public WebTestClient.ResponseSpec maxAge(String name, Matcher<? super Long> matcher) {
long maxAge = getCookie(name).getMaxAge().getSeconds();
this.exchangeResult.assertWithDiagnostics(() -> {
String message = getMessage(name) + " maxAge";
assertThat(message, maxAge, matcher);
});
return this.responseSpec;
}
/**
* Assert a cookie's path attribute.
*/
public WebTestClient.ResponseSpec path(String name, String expected) {
String path = getCookie(name).getPath();
this.exchangeResult.assertWithDiagnostics(() -> {
String message = getMessage(name) + " path";
AssertionErrors.assertEquals(message, expected, path);
});
return this.responseSpec;
}
/**
* Assert a cookie's path attribute with a Hamcrest {@link Matcher}.
*/
public WebTestClient.ResponseSpec path(String name, Matcher<? super String> matcher) {
String path = getCookie(name).getPath();
this.exchangeResult.assertWithDiagnostics(() -> {
String message = getMessage(name) + " path";
assertThat(message, path, matcher);
});
return this.responseSpec;
}
/**
* Assert a cookie's domain attribute.
*/
public WebTestClient.ResponseSpec domain(String name, String expected) {
String path = getCookie(name).getDomain();
this.exchangeResult.assertWithDiagnostics(() -> {
String message = getMessage(name) + " domain";
AssertionErrors.assertEquals(message, expected, path);
});
return this.responseSpec;
}
/**
* Assert a cookie's domain attribute with a Hamcrest {@link Matcher}.
*/
public WebTestClient.ResponseSpec domain(String name, Matcher<? super String> matcher) {
String domain = getCookie(name).getDomain();
this.exchangeResult.assertWithDiagnostics(() -> {
String message = getMessage(name) + " domain";
assertThat(message, domain, matcher);
});
return this.responseSpec;
}
/**
* Assert a cookie's secure attribute.
*/
public WebTestClient.ResponseSpec secure(String name, boolean expected) {
boolean isSecure = getCookie(name).isSecure();
this.exchangeResult.assertWithDiagnostics(() -> {
String message = getMessage(name) + " secure";
AssertionErrors.assertEquals(message, expected, isSecure);
});
return this.responseSpec;
}
/**
* Assert a cookie's httpOnly attribute.
*/
public WebTestClient.ResponseSpec httpOnly(String name, boolean expected) {
boolean isHttpOnly = getCookie(name).isHttpOnly();
this.exchangeResult.assertWithDiagnostics(() -> {
String message = getMessage(name) + " secure";
AssertionErrors.assertEquals(message, expected, isHttpOnly);
});
return this.responseSpec;
}
/**
* Assert a cookie's sameSite attribute.
*/
public WebTestClient.ResponseSpec sameSite(String name, String expected) {
String sameSite = getCookie(name).getSameSite();
this.exchangeResult.assertWithDiagnostics(() -> {
String message = getMessage(name) + " secure";
AssertionErrors.assertEquals(message, expected, sameSite);
});
return this.responseSpec;
}
private ResponseCookie getCookie(String name) {
ResponseCookie cookie = this.exchangeResult.getResponseCookies().getFirst(name);
if (cookie == null) {
String message = "No cookie with name '" + name + "'";
this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.fail(message));
}
return cookie;
}
private String getMessage(String cookie) {
return "Response cookie '" + cookie + "'";
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* 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.
@ -306,8 +306,8 @@ class DefaultWebTestClient implements WebTestClient {
public ResponseSpec exchange() {
ClientResponse clientResponse = this.bodySpec.exchange().block(getTimeout());
Assert.state(clientResponse != null, "No ClientResponse");
WiretapConnector.Info info = wiretapConnector.claimRequest(this.requestId);
return new DefaultResponseSpec(info, clientResponse, this.uriTemplate, getTimeout());
ExchangeResult result = wiretapConnector.getExchangeResult(this.requestId, this.uriTemplate, getTimeout());
return new DefaultResponseSpec(result, clientResponse, getTimeout());
}
}
@ -321,10 +321,8 @@ class DefaultWebTestClient implements WebTestClient {
private final Duration timeout;
DefaultResponseSpec(WiretapConnector.Info wiretapInfo, ClientResponse response,
@Nullable String uriTemplate, Duration timeout) {
this.exchangeResult = wiretapInfo.createExchangeResult(timeout, uriTemplate);
DefaultResponseSpec(ExchangeResult exchangeResult, ClientResponse response, Duration timeout) {
this.exchangeResult = exchangeResult;
this.response = response;
this.timeout = timeout;
}
@ -339,6 +337,11 @@ class DefaultWebTestClient implements WebTestClient {
return new HeaderAssertions(this.exchangeResult, this);
}
@Override
public CookieAssertions expectCookie() {
return new CookieAssertions(this.exchangeResult, this);
}
@Override
public <B> BodySpec<B, ?> expectBody(Class<B> bodyType) {
B body = this.response.bodyToMono(bodyType).block(this.timeout);
@ -380,7 +383,14 @@ class DefaultWebTestClient implements WebTestClient {
@Override
public <T> FluxExchangeResult<T> returnResult(Class<T> elementClass) {
Flux<T> body = this.response.bodyToFlux(elementClass);
Flux<T> body;
if (elementClass.equals(Void.class)) {
this.response.releaseBody().block();
body = Flux.empty();
}
else {
body = this.response.bodyToFlux(elementClass);
}
return new FluxExchangeResult<>(this.exchangeResult, body);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* 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.
@ -72,6 +72,9 @@ public class ExchangeResult {
@Nullable
private final String uriTemplate;
@Nullable
final Object mockServerResult;
/**
* Create an instance with an HTTP request and response along with promises
@ -83,9 +86,11 @@ public class ExchangeResult {
* @param responseBody capture of serialized response body content
* @param timeout how long to wait for content to materialize
* @param uriTemplate the URI template used to set up the request, if any
* @param serverResult the result of a mock server exchange if applicable.
*/
ExchangeResult(ClientHttpRequest request, ClientHttpResponse response,
Mono<byte[]> requestBody, Mono<byte[]> responseBody, Duration timeout, @Nullable String uriTemplate) {
Mono<byte[]> requestBody, Mono<byte[]> responseBody, Duration timeout, @Nullable String uriTemplate,
@Nullable Object serverResult) {
Assert.notNull(request, "ClientHttpRequest is required");
Assert.notNull(response, "ClientHttpResponse is required");
@ -98,6 +103,7 @@ public class ExchangeResult {
this.responseBody = responseBody;
this.timeout = timeout;
this.uriTemplate = uriTemplate;
this.mockServerResult = serverResult;
}
/**
@ -110,6 +116,7 @@ public class ExchangeResult {
this.responseBody = other.responseBody;
this.timeout = other.timeout;
this.uriTemplate = other.uriTemplate;
this.mockServerResult = other.mockServerResult;
}
@ -195,6 +202,16 @@ public class ExchangeResult {
return this.responseBody.block(this.timeout);
}
/**
* Return the result from the mock server exchange, if applicable, for
* further assertions on the state of the server response.
* @since 5.3
* @see org.springframework.test.web.servlet.client.MockMvcTestClient#resultActionsFor(ExchangeResult)
*/
@Nullable
public Object getMockServerResult() {
return this.mockServerResult;
}
/**
* Execute the given Runnable, catch any {@link AssertionError}, decorate
@ -222,7 +239,8 @@ public class ExchangeResult {
"< " + getStatus() + " " + getStatus().getReasonPhrase() + "\n" +
"< " + formatHeaders(getResponseHeaders(), "\n< ") + "\n" +
"\n" +
formatBody(getResponseHeaders().getContentType(), this.responseBody) +"\n";
formatBody(getResponseHeaders().getContentType(), this.responseBody) +"\n" +
formatMockServerResult();
}
private String formatHeaders(HttpHeaders headers, String delimiter) {
@ -252,4 +270,10 @@ public class ExchangeResult {
.block(this.timeout);
}
private String formatMockServerResult() {
return (this.mockServerResult != null ?
"\n====================== MockMvc (Server) ===============================\n" +
this.mockServerResult + "\n" : "");
}
}

View File

@ -16,6 +16,7 @@
package org.springframework.test.web.reactive.server;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
@ -31,6 +32,10 @@ import org.springframework.lang.Nullable;
import org.springframework.test.util.AssertionErrors;
import org.springframework.util.CollectionUtils;
import static org.springframework.test.util.AssertionErrors.assertEquals;
import static org.springframework.test.util.AssertionErrors.assertNotNull;
import static org.springframework.test.util.AssertionErrors.assertTrue;
/**
* Assertions on headers of the response.
*
@ -57,7 +62,42 @@ public class HeaderAssertions {
* Expect a header with the given name to match the specified values.
*/
public WebTestClient.ResponseSpec valueEquals(String headerName, String... values) {
return assertHeader(headerName, Arrays.asList(values), getHeaders().get(headerName));
return assertHeader(headerName, Arrays.asList(values), getHeaders().getOrEmpty(headerName));
}
/**
* Expect a header with the given name to match the given long value.
* @since 5.3
*/
public WebTestClient.ResponseSpec valueEquals(String headerName, long value) {
String actual = getHeaders().getFirst(headerName);
this.exchangeResult.assertWithDiagnostics(() ->
assertTrue("Response does not contain header '" + headerName + "'", actual != null));
return assertHeader(headerName, value, Long.parseLong(actual));
}
/**
* Expect a header with the given name to match the specified long value
* parsed into a date using the preferred date format described in RFC 7231.
* <p>An {@link AssertionError} is thrown if the response does not contain
* the specified header, or if the supplied {@code value} does not match the
* primary header value.
* @since 5.3
*/
public WebTestClient.ResponseSpec valueEqualsDate(String headerName, long value) {
this.exchangeResult.assertWithDiagnostics(() -> {
String headerValue = getHeaders().getFirst(headerName);
assertNotNull("Response does not contain header '" + headerName + "'", headerValue);
HttpHeaders headers = new HttpHeaders();
headers.setDate("expected", value);
headers.set("actual", headerValue);
assertEquals("Response header '" + headerName + "'='" + headerValue + "' " +
"does not match expected value '" + headers.getFirst("expected") + "'",
headers.getFirstDate("expected"), headers.getFirstDate("actual"));
});
return this.responseSpec;
}
/**
@ -106,8 +146,11 @@ public class HeaderAssertions {
* @since 5.1
*/
public WebTestClient.ResponseSpec value(String name, Matcher<? super String> matcher) {
String value = getRequiredValue(name);
this.exchangeResult.assertWithDiagnostics(() -> MatcherAssert.assertThat(value, matcher));
String value = getHeaders().getFirst(name);
this.exchangeResult.assertWithDiagnostics(() -> {
String message = getMessage(name);
MatcherAssert.assertThat(message, value, matcher);
});
return this.responseSpec;
}
@ -118,8 +161,11 @@ public class HeaderAssertions {
* @since 5.3
*/
public WebTestClient.ResponseSpec values(String name, Matcher<Iterable<String>> matcher) {
List<String> values = getRequiredValues(name);
this.exchangeResult.assertWithDiagnostics(() -> MatcherAssert.assertThat(values, matcher));
List<String> values = getHeaders().get(name);
this.exchangeResult.assertWithDiagnostics(() -> {
String message = getMessage(name);
MatcherAssert.assertThat(message, values, matcher);
});
return this.responseSpec;
}
@ -154,7 +200,8 @@ public class HeaderAssertions {
private List<String> getRequiredValues(String name) {
List<String> values = getHeaders().get(name);
if (CollectionUtils.isEmpty(values)) {
AssertionErrors.fail(getMessage(name) + " not found");
this.exchangeResult.assertWithDiagnostics(() ->
AssertionErrors.fail(getMessage(name) + " not found"));
}
return values;
}
@ -249,6 +296,14 @@ public class HeaderAssertions {
return assertHeader("Last-Modified", lastModified, getHeaders().getLastModified());
}
/**
* Expect a "Location" header with the given value.
* @since 5.3
*/
public WebTestClient.ResponseSpec location(String location) {
return assertHeader("Location", URI.create(location), getHeaders().getLocation());
}
private HttpHeaders getHeaders() {
return this.exchangeResult.getResponseHeaders();

View File

@ -0,0 +1,35 @@
/*
* 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.reactive.server;
import org.springframework.http.client.reactive.ClientHttpResponse;
/**
* Simple {@link ClientHttpResponse} extension that also exposes a result object
* from the underlying mock server exchange for further assertions on the state
* of the server response after the request is performed.
*
* @author Rossen Stoyanchev
* @since 5.3
*/
public interface MockServerClientHttpResponse extends ClientHttpResponse {
/**
* Return the result object with the server request and response.
*/
Object getServerResult();
}

View File

@ -172,7 +172,7 @@ public interface WebTestClient {
// Static factory methods
/**
* Use this server setup to test one `@Controller` at a time.
* Use this server setup to test one {@code @Controller} at a time.
* This option loads the default configuration of
* {@link org.springframework.web.reactive.config.EnableWebFlux @EnableWebFlux}.
* There are builder methods to customize the Java config. The resulting
@ -229,8 +229,8 @@ public interface WebTestClient {
}
/**
* This server setup option allows you to connect to a running server via
* Reactor Netty.
* This server setup option allows you to connect to a live server through
* a Reactor Netty client connector.
* <p><pre class="code">
* WebTestClient client = WebTestClient.bindToServer()
* .baseUrl("http://localhost:8080")
@ -244,11 +244,6 @@ public interface WebTestClient {
/**
* A variant of {@link #bindToServer()} with a pre-configured connector.
* <p><pre class="code">
* WebTestClient client = WebTestClient.bindToServer()
* .baseUrl("http://localhost:8080")
* .build();
* </pre>
* @return chained API to customize client config
* @since 5.0.2
*/
@ -769,6 +764,12 @@ public interface WebTestClient {
*/
HeaderAssertions expectHeader();
/**
* Assertions on the cookies of the response.
* @since 5.3
*/
CookieAssertions expectCookie();
/**
* Consume and decode the response body to a single object of type
* {@code <B>} and then apply assertions.
@ -802,18 +803,13 @@ public interface WebTestClient {
BodyContentSpec expectBody();
/**
* Exit the chained API and consume the response body externally. This
* is useful for testing infinite streams (e.g. SSE) where you need to
* to assert decoded objects as they come and then cancel at some point
* when test objectives are met. Consider using {@code StepVerifier}
* from {@literal "reactor-test"} to assert the {@code Flux<T>} stream
* of decoded objects.
* Exit the chained flow in order to consume the response body
* externally, e.g. via {@link reactor.test.StepVerifier}.
*
* <p><strong>Note:</strong> Do not use this option for cases where there
* is no content (e.g. 204, 4xx) or you're not interested in the content.
* For such cases you can use {@code expectBody().isEmpty()} or
* {@code expectBody(Void.class)} which ensures that resources are
* released regardless of whether the response has content or not.
* <p>Note that when {@code Void.class} is passed in, the response body
* is consumed and released. If no content is expected, then consider
* using {@code .expectBody().isEmpty()} instead which asserts that
* there is no content.
*/
<T> FluxExchangeResult<T> returnResult(Class<T> elementClass);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* 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.
@ -53,7 +53,7 @@ class WiretapConnector implements ClientHttpConnector {
private final ClientHttpConnector delegate;
private final Map<String, Info> exchanges = new ConcurrentHashMap<>();
private final Map<String, ClientExchangeInfo> exchanges = new ConcurrentHashMap<>();
WiretapConnector(ClientHttpConnector delegate) {
@ -79,43 +79,48 @@ class WiretapConnector implements ClientHttpConnector {
String requestId = wrappedRequest.getHeaders().getFirst(header);
Assert.state(requestId != null, () -> "No \"" + header + "\" header");
WiretapClientHttpResponse wrappedResponse = new WiretapClientHttpResponse(response);
this.exchanges.put(requestId, new Info(wrappedRequest, wrappedResponse));
this.exchanges.put(requestId, new ClientExchangeInfo(wrappedRequest, wrappedResponse));
return wrappedResponse;
});
}
/**
* Retrieve the {@link Info} for the given "request-id" header value.
* Create the {@link ExchangeResult} for the given "request-id" header value.
*/
public Info claimRequest(String requestId) {
Info info = this.exchanges.remove(requestId);
Assert.state(info != null, () -> {
ExchangeResult getExchangeResult(String requestId, @Nullable String uriTemplate, Duration timeout) {
ClientExchangeInfo clientInfo = this.exchanges.remove(requestId);
Assert.state(clientInfo != null, () -> {
String header = WebTestClient.WEBTESTCLIENT_REQUEST_ID;
return "No match for " + header + "=" + requestId;
});
return info;
return new ExchangeResult(clientInfo.getRequest(), clientInfo.getResponse(),
clientInfo.getRequest().getRecorder().getContent(),
clientInfo.getResponse().getRecorder().getContent(),
timeout, uriTemplate,
clientInfo.getResponse().getMockServerResult());
}
/**
* Holder for {@link WiretapClientHttpRequest} and {@link WiretapClientHttpResponse}.
*/
class Info {
private static class ClientExchangeInfo {
private final WiretapClientHttpRequest request;
private final WiretapClientHttpResponse response;
public Info(WiretapClientHttpRequest request, WiretapClientHttpResponse response) {
public ClientExchangeInfo(WiretapClientHttpRequest request, WiretapClientHttpResponse response) {
this.request = request;
this.response = response;
}
public WiretapClientHttpRequest getRequest() {
return this.request;
}
public ExchangeResult createExchangeResult(Duration timeout, @Nullable String uriTemplate) {
return new ExchangeResult(this.request, this.response, this.request.getRecorder().getContent(),
this.response.getRecorder().getContent(), timeout, uriTemplate);
public WiretapClientHttpResponse getResponse() {
return this.response;
}
}
@ -275,6 +280,12 @@ class WiretapConnector implements ClientHttpConnector {
public Flux<DataBuffer> getBody() {
return Flux.from(this.recorder.getPublisherToUse());
}
@Nullable
public Object getMockServerResult() {
return (getDelegate() instanceof MockServerClientHttpResponse ?
((MockServerClientHttpResponse) getDelegate()).getServerResult() : null);
}
}
}

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;

View File

@ -107,6 +107,7 @@ public abstract class AbstractMockMvcBuilder<B extends AbstractMockMvcBuilder<B>
return self();
}
@Override
public final <T extends B> T addDispatcherServletCustomizer(DispatcherServletCustomizer customizer) {
this.dispatcherServletCustomizers.add(customizer);
return self();

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* 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.
@ -18,6 +18,7 @@ package org.springframework.test.web.servlet.setup;
import javax.servlet.Filter;
import org.springframework.test.web.servlet.DispatcherServletCustomizer;
import org.springframework.test.web.servlet.MockMvcBuilder;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.ResultHandler;
@ -37,7 +38,7 @@ public interface ConfigurableMockMvcBuilder<B extends ConfigurableMockMvcBuilder
* <pre class="code">
* mockMvcBuilder.addFilters(springSecurityFilterChain);
* </pre>
* <p>is the equivalent of the following web.xml configuration:
* <p>It 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;
@ -52,9 +53,9 @@ public interface ConfigurableMockMvcBuilder<B extends ConfigurableMockMvcBuilder
/**
* Add a filter mapped to a specific set of patterns. For example:
* <pre class="code">
* mockMvcBuilder.addFilters(myResourceFilter, "/resources/*");
* mockMvcBuilder.addFilter(myResourceFilter, "/resources/*");
* </pre>
* <p>is the equivalent of:
* <p>It is the equivalent of:
* <pre class="code">
* &lt;filter-mapping&gt;
* &lt;filter-name&gt;myResourceFilter&lt;/filter-name&gt;
@ -105,6 +106,13 @@ public interface ConfigurableMockMvcBuilder<B extends ConfigurableMockMvcBuilder
*/
<T extends B> T dispatchOptions(boolean dispatchOptions);
/**
* A more advanced variant of {@link #dispatchOptions(boolean)} that allows
* customizing any {@link org.springframework.web.servlet.DispatcherServlet}
* property.
*/
<T extends B> T addDispatcherServletCustomizer(DispatcherServletCustomizer customizer);
/**
* Add a {@code MockMvcConfigurer} that automates MockMvc setup and
* configures it for some specific purpose (e.g. security).

View File

@ -0,0 +1,138 @@
/*
* 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.reactive.server;
import java.net.URI;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.MonoProcessor;
import reactor.core.publisher.Sinks;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.mock.http.client.reactive.MockClientHttpRequest;
import org.springframework.mock.http.client.reactive.MockClientHttpResponse;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Mockito.mock;
/**
* Unit tests for {@link CookieAssertions}
* @author Rossen Stoyanchev
*/
public class CookieAssertionTests {
private final ResponseCookie cookie = ResponseCookie.from("foo", "bar")
.maxAge(Duration.ofMinutes(30))
.domain("foo.com")
.path("/foo")
.secure(true)
.httpOnly(true)
.sameSite("Lax")
.build();
private final CookieAssertions assertions = cookieAssertions(cookie);
@Test
void valueEquals() {
assertions.valueEquals("foo", "bar");
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.valueEquals("what?!", "bar"));
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.valueEquals("foo", "what?!"));
}
@Test
void value() {
assertions.value("foo", equalTo("bar"));
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.value("foo", equalTo("what?!")));
}
@Test
void exists() {
assertions.exists("foo");
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.exists("what?!"));
}
@Test
void doesNotExist() {
assertions.doesNotExist("what?!");
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.doesNotExist("foo"));
}
@Test
void maxAge() {
assertions.maxAge("foo", Duration.ofMinutes(30));
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> assertions.maxAge("foo", Duration.ofMinutes(29)));
assertions.maxAge("foo", equalTo(Duration.ofMinutes(30).getSeconds()));
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> assertions.maxAge("foo", equalTo(Duration.ofMinutes(29).getSeconds())));
}
@Test
void domain() {
assertions.domain("foo", "foo.com");
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.domain("foo", "what.com"));
assertions.domain("foo", equalTo("foo.com"));
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.domain("foo", equalTo("what.com")));
}
@Test
void path() {
assertions.path("foo", "/foo");
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.path("foo", "/what"));
assertions.path("foo", equalTo("/foo"));
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.path("foo", equalTo("/what")));
}
@Test
void secure() {
assertions.secure("foo", true);
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.secure("foo", false));
}
@Test
void httpOnly() {
assertions.httpOnly("foo", true);
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.httpOnly("foo", false));
}
@Test
void sameSite() {
assertions.sameSite("foo", "Lax");
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.sameSite("foo", "Strict"));
}
private CookieAssertions cookieAssertions(ResponseCookie cookie) {
MockClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, URI.create("/"));
MockClientHttpResponse response = new MockClientHttpResponse(HttpStatus.OK);
response.getCookies().add(cookie.getName(), cookie);
MonoProcessor<byte[]> emptyContent = MonoProcessor.fromSink(Sinks.one());
emptyContent.onComplete();
ExchangeResult result = new ExchangeResult(request, response, emptyContent, emptyContent, Duration.ZERO, null, null);
return new CookieAssertions(result, mock(WebTestClient.ResponseSpec.class));
}
}

View File

@ -244,7 +244,7 @@ class HeaderAssertionTests {
MonoProcessor<byte[]> emptyContent = MonoProcessor.fromSink(Sinks.one());
emptyContent.onComplete();
ExchangeResult result = new ExchangeResult(request, response, emptyContent, emptyContent, Duration.ZERO, null);
ExchangeResult result = new ExchangeResult(request, response, emptyContent, emptyContent, Duration.ZERO, null, null);
return new HeaderAssertions(result, mock(WebTestClient.ResponseSpec.class));
}

View File

@ -160,7 +160,7 @@ class StatusAssertionTests {
MonoProcessor<byte[]> emptyContent = MonoProcessor.fromSink(Sinks.one());
emptyContent.onComplete();
ExchangeResult result = new ExchangeResult(request, response, emptyContent, emptyContent, Duration.ZERO, null);
ExchangeResult result = new ExchangeResult(request, response, emptyContent, emptyContent, Duration.ZERO, null, null);
return new StatusAssertions(result, mock(WebTestClient.ResponseSpec.class));
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* 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.
@ -57,8 +57,7 @@ public class WiretapConnectorTests {
ExchangeFunction function = ExchangeFunctions.create(wiretapConnector);
function.exchange(clientRequest).block(ofMillis(0));
WiretapConnector.Info actual = wiretapConnector.claimRequest("1");
ExchangeResult result = actual.createExchangeResult(Duration.ZERO, null);
ExchangeResult result = wiretapConnector.getExchangeResult("1", null, Duration.ZERO);
assertThat(result.getMethod()).isEqualTo(HttpMethod.GET);
assertThat(result.getUrl().toString()).isEqualTo("/test");
}

View File

@ -0,0 +1,119 @@
/*
* 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.samples.client.context;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Callable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import static org.mockito.ArgumentMatchers.any;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.context.AsyncControllerJavaConfigTests}.
*
* @author Rossen Stoyanchev
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextHierarchy(@ContextConfiguration(classes = AsyncControllerJavaConfigTests.WebConfig.class))
public class AsyncControllerJavaConfigTests {
@Autowired
private WebApplicationContext wac;
@Autowired
private CallableProcessingInterceptor callableInterceptor;
private WebTestClient testClient;
@BeforeEach
public void setup() {
this.testClient = MockMvcTestClient.bindToApplicationContext(this.wac).build();
}
@Test
public void callableInterceptor() throws Exception {
testClient.get().uri("/callable")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().json("{\"key\":\"value\"}");
Mockito.verify(this.callableInterceptor).beforeConcurrentHandling(any(), any());
Mockito.verify(this.callableInterceptor).preProcess(any(), any());
Mockito.verify(this.callableInterceptor).postProcess(any(), any(), any());
Mockito.verify(this.callableInterceptor).afterCompletion(any(), any());
Mockito.verifyNoMoreInteractions(this.callableInterceptor);
}
@Configuration
@EnableWebMvc
static class WebConfig implements WebMvcConfigurer {
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.registerCallableInterceptors(callableInterceptor());
}
@Bean
public CallableProcessingInterceptor callableInterceptor() {
return Mockito.mock(CallableProcessingInterceptor.class);
}
@Bean
public AsyncController asyncController() {
return new AsyncController();
}
}
@RestController
static class AsyncController {
@GetMapping("/callable")
public Callable<Map<String, String>> getCallable() {
return () -> Collections.singletonMap("key", "value");
}
}
}

View File

@ -0,0 +1,148 @@
/*
* 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.samples.client.context;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.test.web.servlet.samples.context.PersonController;
import org.springframework.test.web.servlet.samples.context.PersonDao;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import static org.mockito.BDDMockito.given;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.context.JavaConfigTests}.
*
* @author Rossen Stoyanchev
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration("classpath:META-INF/web-resources")
@ContextHierarchy({
@ContextConfiguration(classes = JavaConfigTests.RootConfig.class),
@ContextConfiguration(classes = JavaConfigTests.WebConfig.class)
})
public class JavaConfigTests {
@Autowired
private WebApplicationContext wac;
@Autowired
private PersonDao personDao;
@Autowired
private PersonController personController;
private WebTestClient testClient;
@BeforeEach
public void setup() {
this.testClient = MockMvcTestClient.bindToApplicationContext(this.wac).build();
given(this.personDao.getPerson(5L)).willReturn(new Person("Joe"));
}
@Test
public void person() {
testClient.get().uri("/person/5")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().json("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
}
@Test
public void tilesDefinitions() {
testClient.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("Forwarded-Url", "/WEB-INF/layouts/standardLayout.jsp");
}
@Configuration
static class RootConfig {
@Bean
public PersonDao personDao() {
return Mockito.mock(PersonDao.class);
}
}
@Configuration
@EnableWebMvc
static class WebConfig implements WebMvcConfigurer {
@Autowired
private RootConfig rootConfig;
@Bean
public PersonController personController() {
return new PersonController(this.rootConfig.personDao());
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.tiles();
}
@Bean
public TilesConfigurer tilesConfigurer() {
TilesConfigurer configurer = new TilesConfigurer();
configurer.setDefinitions("/WEB-INF/**/tiles.xml");
return configurer;
}
}
}

View File

@ -0,0 +1,98 @@
/*
* 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.samples.client.context;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.handler;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.context.WebAppResourceTests}.
*
* @author Rossen Stoyanchev
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration("src/test/resources/META-INF/web-resources")
@ContextHierarchy({
@ContextConfiguration("../../context/root-context.xml"),
@ContextConfiguration("../../context/servlet-context.xml")
})
public class WebAppResourceTests {
@Autowired
private WebApplicationContext wac;
private WebTestClient testClient;
@BeforeEach
public void setup() {
this.testClient = MockMvcTestClient.bindToApplicationContext(this.wac).build();
}
// TilesConfigurer: resources under "/WEB-INF/**/tiles.xml"
@Test
public void tilesDefinitions() {
testClient.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("Forwarded-Url", "/WEB-INF/layouts/standardLayout.jsp");
}
// Resources served via <mvc:resources/>
@Test
public void resourceRequest() {
testClient.get().uri("/resources/Spring.js")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType("application/javascript")
.expectBody(String.class).value(containsString("Spring={};"));
}
// Forwarded to the "default" servlet via <mvc:default-servlet-handler/>
@Test
public void resourcesViaDefaultServlet() throws Exception {
EntityExchangeResult<Void> result = testClient.get().uri("/unknown/resource")
.exchange()
.expectStatus().isOk()
.expectBody().isEmpty();
MockMvcTestClient.resultActionsFor(result)
.andExpect(handler().handlerType(DefaultServletHttpRequestHandler.class))
.andExpect(forwardedUrl("default"));
}
}

View File

@ -0,0 +1,84 @@
/*
* 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.samples.client.context;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.test.web.servlet.samples.context.PersonDao;
import org.springframework.web.context.WebApplicationContext;
import static org.mockito.BDDMockito.given;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.context.XmlConfigTests}.
*
* @author Rossen Stoyanchev
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration("src/test/resources/META-INF/web-resources")
@ContextHierarchy({
@ContextConfiguration("../../context/root-context.xml"),
@ContextConfiguration("../../context/servlet-context.xml")
})
public class XmlConfigTests {
@Autowired
private WebApplicationContext wac;
@Autowired
private PersonDao personDao;
private WebTestClient testClient;
@BeforeEach
public void setup() {
this.testClient = MockMvcTestClient.bindToApplicationContext(this.wac).build();
given(this.personDao.getPerson(5L)).willReturn(new Person("Joe"));
}
@Test
public void person() {
testClient.get().uri("/person/5")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().json("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
}
@Test
public void tilesDefinitions() {
testClient.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("Forwarded-Url", "/WEB-INF/layouts/standardLayout.jsp");
}
}

View File

@ -0,0 +1,221 @@
/*
* 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.samples.client.standalone;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureTask;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.AsyncTests}.
*
* @author Rossen Stoyanchev
*/
public class AsyncTests {
private final WebTestClient testClient =
MockMvcTestClient.bindToController(new AsyncController()).build();
@Test
public void callable() {
this.testClient.get()
.uri("/1?callable=true")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody().json("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
}
@Test
public void streaming() {
this.testClient.get()
.uri("/1?streaming=true")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("name=Joe");
}
@Test
public void streamingSlow() {
this.testClient.get()
.uri("/1?streamingSlow=true")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("name=Joe&someBoolean=true");
}
@Test
public void streamingJson() {
this.testClient.get()
.uri("/1?streamingJson=true")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody().json("{\"name\":\"Joe\",\"someDouble\":0.5}");
}
@Test
public void deferredResult() {
this.testClient.get()
.uri("/1?deferredResult=true")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody().json("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
}
@Test
public void deferredResultWithImmediateValue() throws Exception {
this.testClient.get()
.uri("/1?deferredResultWithImmediateValue=true")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody().json("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
}
@Test
public void deferredResultWithDelayedError() throws Exception {
this.testClient.get()
.uri("/1?deferredResultWithDelayedError=true")
.exchange()
.expectStatus().is5xxServerError()
.expectBody(String.class).isEqualTo("Delayed Error");
}
@Test
public void listenableFuture() throws Exception {
this.testClient.get()
.uri("/1?listenableFuture=true")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody().json("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
}
@Test
public void completableFutureWithImmediateValue() throws Exception {
this.testClient.get()
.uri("/1?completableFutureWithImmediateValue=true")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody().json("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
}
@RestController
@RequestMapping(path = "/{id}", produces = "application/json")
private static class AsyncController {
@RequestMapping(params = "callable")
public Callable<Person> getCallable() {
return () -> new Person("Joe");
}
@RequestMapping(params = "streaming")
public StreamingResponseBody getStreaming() {
return os -> os.write("name=Joe".getBytes(StandardCharsets.UTF_8));
}
@RequestMapping(params = "streamingSlow")
public StreamingResponseBody getStreamingSlow() {
return os -> {
os.write("name=Joe".getBytes());
try {
Thread.sleep(200);
os.write("&someBoolean=true".getBytes(StandardCharsets.UTF_8));
}
catch (InterruptedException e) {
/* no-op */
}
};
}
@RequestMapping(params = "streamingJson")
public ResponseEntity<StreamingResponseBody> getStreamingJson() {
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON)
.body(os -> os.write("{\"name\":\"Joe\",\"someDouble\":0.5}".getBytes(StandardCharsets.UTF_8)));
}
@RequestMapping(params = "deferredResult")
public DeferredResult<Person> getDeferredResult() {
DeferredResult<Person> result = new DeferredResult<>();
delay(100, () -> result.setResult(new Person("Joe")));
return result;
}
@RequestMapping(params = "deferredResultWithImmediateValue")
public DeferredResult<Person> getDeferredResultWithImmediateValue() {
DeferredResult<Person> result = new DeferredResult<>();
result.setResult(new Person("Joe"));
return result;
}
@RequestMapping(params = "deferredResultWithDelayedError")
public DeferredResult<Person> getDeferredResultWithDelayedError() {
DeferredResult<Person> result = new DeferredResult<>();
delay(100, () -> result.setErrorResult(new RuntimeException("Delayed Error")));
return result;
}
@RequestMapping(params = "listenableFuture")
public ListenableFuture<Person> getListenableFuture() {
ListenableFutureTask<Person> futureTask = new ListenableFutureTask<>(() -> new Person("Joe"));
delay(100, futureTask);
return futureTask;
}
@RequestMapping(params = "completableFutureWithImmediateValue")
public CompletableFuture<Person> getCompletableFutureWithImmediateValue() {
CompletableFuture<Person> future = new CompletableFuture<>();
future.complete(new Person("Joe"));
return future;
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String errorHandler(Exception ex) {
return ex.getMessage();
}
private void delay(long millis, Runnable task) {
Mono.delay(Duration.ofMillis(millis)).doOnTerminate(task).subscribe();
}
}
}

View File

@ -0,0 +1,238 @@
/*
* 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.samples.client.standalone;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.ExceptionHandlerTests}.
*
* @author Rossen Stoyanchev
*/
class ExceptionHandlerTests {
@Nested
class MvcTests {
@Test
void localExceptionHandlerMethod() {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController()).build();
client.get().uri("/person/Clyde")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("Forwarded-Url", "errorView");
}
@Test
void globalExceptionHandlerMethod() {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController())
.controllerAdvice(new GlobalExceptionHandler())
.build();
client.get().uri("/person/Bonnie")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("Forwarded-Url", "globalErrorView");
}
}
@Controller
private static class PersonController {
@GetMapping("/person/{name}")
String show(@PathVariable String name) {
if (name.equals("Clyde")) {
throw new IllegalArgumentException("simulated exception");
}
else if (name.equals("Bonnie")) {
throw new IllegalStateException("simulated exception");
}
return "person/show";
}
@ExceptionHandler
String handleException(IllegalArgumentException exception) {
return "errorView";
}
}
@ControllerAdvice
private static class GlobalExceptionHandler {
@ExceptionHandler
String handleException(IllegalStateException exception) {
return "globalErrorView";
}
}
@Nested
class RestTests {
@Test
void noException() {
WebTestClient client = MockMvcTestClient.bindToController(new RestPersonController())
.controllerAdvice(new RestPersonControllerExceptionHandler())
.build();
client.get().uri("/person/Yoda")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.name", "Yoda");
}
@Test
void localExceptionHandlerMethod() {
WebTestClient client = MockMvcTestClient.bindToController(new RestPersonController())
.controllerAdvice(new RestPersonControllerExceptionHandler())
.build();
client.get().uri("/person/Luke")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.error", "local - IllegalArgumentException");
}
@Test
void globalExceptionHandlerMethod() {
WebTestClient client = MockMvcTestClient.bindToController(new RestPersonController())
.controllerAdvice(new RestGlobalExceptionHandler())
.build();
client.get().uri("/person/Leia")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.error", "global - IllegalArgumentException");
}
@Test
void globalRestPersonControllerExceptionHandlerTakesPrecedenceOverGlobalExceptionHandler() {
WebTestClient client = MockMvcTestClient.bindToController(new RestPersonController())
.controllerAdvice(RestGlobalExceptionHandler.class, RestPersonControllerExceptionHandler.class)
.build();
client.get().uri("/person/Leia")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.error", "globalPersonController - IllegalStateException");
}
@Test
void noHandlerFound() {
WebTestClient client = MockMvcTestClient.bindToController(new RestPersonController())
.controllerAdvice(RestGlobalExceptionHandler.class, RestPersonControllerExceptionHandler.class)
.dispatcherServletCustomizer(servlet -> servlet.setThrowExceptionIfNoHandlerFound(true))
.build();
client.get().uri("/bogus")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.error", "global - NoHandlerFoundException");
}
}
@RestController
private static class RestPersonController {
@GetMapping("/person/{name}")
Person get(@PathVariable String name) {
switch (name) {
case "Luke":
throw new IllegalArgumentException();
case "Leia":
throw new IllegalStateException();
default:
return new Person("Yoda");
}
}
@ExceptionHandler
Error handleException(IllegalArgumentException exception) {
return new Error("local - " + exception.getClass().getSimpleName());
}
}
@RestControllerAdvice(assignableTypes = RestPersonController.class)
@Order(Ordered.HIGHEST_PRECEDENCE)
private static class RestPersonControllerExceptionHandler {
@ExceptionHandler
Error handleException(Throwable exception) {
return new Error("globalPersonController - " + exception.getClass().getSimpleName());
}
}
@RestControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE)
private static class RestGlobalExceptionHandler {
@ExceptionHandler
Error handleException(Throwable exception) {
return new Error( "global - " + exception.getClass().getSimpleName());
}
}
static class Person {
private final String name;
Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
static class Error {
private final String error;
Error(String error) {
this.error = error;
}
public String getError() {
return error;
}
}
}

View File

@ -0,0 +1,323 @@
/*
* 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.samples.client.standalone;
import java.io.IOException;
import java.security.Principal;
import java.util.concurrent.CompletableFuture;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncListener;
import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.validation.Valid;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.flash;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.FilterTests}.
*
* @author Rossen Stoyanchev
*/
public class FilterTests {
@Test
public void whenFiltersCompleteMvcProcessesRequest() throws Exception {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController())
.filters(new ContinueFilter())
.build();
EntityExchangeResult<Void> exchangeResult = client.post().uri("/persons?name=Andy")
.exchange()
.expectStatus().isFound()
.expectHeader().location("/person/1")
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().size(1))
.andExpect(model().attributeExists("id"))
.andExpect(flash().attributeCount(1))
.andExpect(flash().attribute("message", "success!"));
}
@Test
public void filtersProcessRequest() {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController())
.filters(new ContinueFilter(), new RedirectFilter())
.build();
client.post().uri("/persons?name=Andy")
.exchange()
.expectStatus().isFound()
.expectHeader().location("/login");
}
@Test
public void filterMappedBySuffix() {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController())
.filter(new RedirectFilter(), "*.html")
.build();
client.post().uri("/persons.html?name=Andy")
.exchange()
.expectStatus().isFound()
.expectHeader().location("/login");
}
@Test
public void filterWithExactMapping() {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController())
.filter(new RedirectFilter(), "/p", "/persons")
.build();
client.post().uri("/persons?name=Andy")
.exchange()
.expectStatus().isFound()
.expectHeader().location("/login");
}
@Test
public void filterSkipped() throws Exception {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController())
.filter(new RedirectFilter(), "/p", "/person")
.build();
EntityExchangeResult<Void> exchangeResult =
client.post().uri("/persons?name=Andy")
.exchange()
.expectStatus().isFound()
.expectHeader().location("/person/1")
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().size(1))
.andExpect(model().attributeExists("id"))
.andExpect(flash().attributeCount(1))
.andExpect(flash().attribute("message", "success!"));
}
@Test
public void filterWrapsRequestResponse() throws Exception {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController())
.filter(new WrappingRequestResponseFilter())
.build();
EntityExchangeResult<Void> exchangeResult =
client.post().uri("/user").exchange().expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attribute("principal", WrappingRequestResponseFilter.PRINCIPAL_NAME));
}
@Test
public void filterWrapsRequestResponseAndPerformsAsyncDispatch() {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController())
.filters(new WrappingRequestResponseFilter(), new ShallowEtagHeaderFilter())
.build();
client.get().uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectHeader().contentLength(53)
.expectHeader().valueEquals("ETag", "\"0e37becb4f0c90709cb2e1efcc61eaa00\"")
.expectBody().json("{\"name\":\"Lukas\",\"someDouble\":0.0,\"someBoolean\":false}");
}
@Controller
private static class PersonController {
@PostMapping(path="/persons")
public String save(@Valid Person person, Errors errors, RedirectAttributes redirectAttrs) {
if (errors.hasErrors()) {
return "person/add";
}
redirectAttrs.addAttribute("id", "1");
redirectAttrs.addFlashAttribute("message", "success!");
return "redirect:/person/{id}";
}
@PostMapping("/user")
public ModelAndView user(Principal principal) {
return new ModelAndView("user/view", "principal", principal.getName());
}
@GetMapping("/forward")
public String forward() {
return "forward:/persons";
}
@GetMapping("persons/{id}")
@ResponseBody
public CompletableFuture<Person> getPerson() {
return CompletableFuture.completedFuture(new Person("Lukas"));
}
}
private class ContinueFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
filterChain.doFilter(request, response);
}
}
private static class WrappingRequestResponseFilter extends OncePerRequestFilter {
public static final String PRINCIPAL_NAME = "WrapRequestResponseFilterPrincipal";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
filterChain.doFilter(new HttpServletRequestWrapper(request) {
@Override
public Principal getUserPrincipal() {
return () -> PRINCIPAL_NAME;
}
// Like Spring Security does in HttpServlet3RequestFactory..
@Override
public AsyncContext getAsyncContext() {
return super.getAsyncContext() != null ?
new AsyncContextWrapper(super.getAsyncContext()) : null;
}
}, new HttpServletResponseWrapper(response));
}
}
private class RedirectFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
response.sendRedirect("/login");
}
}
private static class AsyncContextWrapper implements AsyncContext {
private final AsyncContext delegate;
public AsyncContextWrapper(AsyncContext delegate) {
this.delegate = delegate;
}
@Override
public ServletRequest getRequest() {
return this.delegate.getRequest();
}
@Override
public ServletResponse getResponse() {
return this.delegate.getResponse();
}
@Override
public boolean hasOriginalRequestAndResponse() {
return this.delegate.hasOriginalRequestAndResponse();
}
@Override
public void dispatch() {
this.delegate.dispatch();
}
@Override
public void dispatch(String path) {
this.delegate.dispatch(path);
}
@Override
public void dispatch(ServletContext context, String path) {
this.delegate.dispatch(context, path);
}
@Override
public void complete() {
this.delegate.complete();
}
@Override
public void start(Runnable run) {
this.delegate.start(run);
}
@Override
public void addListener(AsyncListener listener) {
this.delegate.addListener(listener);
}
@Override
public void addListener(AsyncListener listener, ServletRequest req, ServletResponse res) {
this.delegate.addListener(listener, req, res);
}
@Override
public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException {
return this.delegate.createListener(clazz);
}
@Override
public void setTimeout(long timeout) {
this.delegate.setTimeout(timeout);
}
@Override
public long getTimeout() {
return this.delegate.getTimeout();
}
}
}

View File

@ -0,0 +1,112 @@
/*
* 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.samples.client.standalone;
import java.security.Principal;
import org.junit.jupiter.api.Test;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcConfigurerAdapter;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.WebApplicationContext;
import static org.mockito.Mockito.mock;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.FrameworkExtensionTests}.
*
* @author Rossen Stoyanchev
*/
public class FrameworkExtensionTests {
private final WebTestClient client =
MockMvcTestClient.bindToController(new SampleController())
.apply(defaultSetup())
.build();
@Test
public void fooHeader() {
this.client.get().uri("/")
.header("Foo", "a=b")
.exchange()
.expectBody(String.class).isEqualTo("Foo");
}
@Test
public void barHeader() {
this.client.get().uri("/")
.header("Bar", "a=b")
.exchange()
.expectBody(String.class).isEqualTo("Bar");
}
private static TestMockMvcConfigurer defaultSetup() {
return new TestMockMvcConfigurer();
}
/**
* Test {@code MockMvcConfigurer}.
*/
private static class TestMockMvcConfigurer extends MockMvcConfigurerAdapter {
@Override
public void afterConfigurerAdded(ConfigurableMockMvcBuilder<?> builder) {
builder.alwaysExpect(status().isOk());
}
@Override
public RequestPostProcessor beforeMockMvcCreated(ConfigurableMockMvcBuilder<?> builder,
WebApplicationContext context) {
return request -> {
request.setUserPrincipal(mock(Principal.class));
return request;
};
}
}
@Controller
@RequestMapping("/")
private static class SampleController {
@RequestMapping(headers = "Foo")
@ResponseBody
public String handleFoo(Principal principal) {
Assert.notNull(principal, "Principal must not be null");
return "Foo";
}
@RequestMapping(headers = "Bar")
@ResponseBody
public String handleBar(Principal principal) {
Assert.notNull(principal, "Principal must not be null");
return "Bar";
}
}
}

View File

@ -0,0 +1,405 @@
/*
* 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.samples.client.standalone;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.multipart.MultipartFile;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.MultipartControllerTests}.
*
* @author Rossen Stoyanchev
*/
public class MultipartControllerTests {
private final WebTestClient testClient = MockMvcTestClient.bindToController(new MultipartController()).build();
@Test
public void multipartRequestWithSingleFile() throws Exception {
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/multipartfile")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attribute("fileContent", fileContent))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWithSingleFileNotPresent() {
testClient.post().uri("/multipartfile")
.exchange()
.expectStatus().isFound();
}
@Test
public void multipartRequestWithFileArray() throws Exception {
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/multipartfilearray")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attribute("fileContent", fileContent))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWithFileArrayNoMultipart() {
testClient.post().uri("/multipartfilearray")
.exchange()
.expectStatus().isFound();
}
@Test
public void multipartRequestWithOptionalFile() throws Exception {
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/optionalfile")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attribute("fileContent", fileContent))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWithOptionalFileNotPresent() throws Exception {
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/optionalfile")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attributeDoesNotExist("fileContent"))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWithOptionalFileArray() throws Exception {
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/optionalfilearray")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attribute("fileContent", fileContent))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWithOptionalFileArrayNotPresent() throws Exception {
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/optionalfilearray")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attributeDoesNotExist("fileContent"))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWithOptionalFileList() throws Exception {
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/optionalfilelist")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attribute("fileContent", fileContent))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWithOptionalFileListNotPresent() throws Exception {
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/optionalfilelist")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attributeDoesNotExist("fileContent"))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWithServletParts() throws Exception {
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/multipartfile")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attribute("fileContent", fileContent))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWrapped() throws Exception {
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
WebTestClient client = MockMvcTestClient.bindToController(new MultipartController())
.filter(new RequestWrappingFilter())
.build();
EntityExchangeResult<Void> exchangeResult = client.post().uri("/multipartfile")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attribute("jsonContent", json));
}
@Controller
private static class MultipartController {
@RequestMapping(value = "/multipartfile", method = RequestMethod.POST)
public String processMultipartFile(@RequestParam(required = false) MultipartFile file,
@RequestPart(required = false) Map<String, String> json, Model model) throws IOException {
if (file != null) {
model.addAttribute("fileContent", file.getBytes());
}
if (json != null) {
model.addAttribute("jsonContent", json);
}
return "redirect:/index";
}
@RequestMapping(value = "/multipartfilearray", method = RequestMethod.POST)
public String processMultipartFileArray(@RequestParam(required = false) MultipartFile[] file,
@RequestPart(required = false) Map<String, String> json, Model model) throws IOException {
if (file != null && file.length > 0) {
byte[] content = file[0].getBytes();
assertThat(file[1].getBytes()).isEqualTo(content);
model.addAttribute("fileContent", content);
}
if (json != null) {
model.addAttribute("jsonContent", json);
}
return "redirect:/index";
}
@RequestMapping(value = "/multipartfilelist", method = RequestMethod.POST)
public String processMultipartFileList(@RequestParam(required = false) List<MultipartFile> file,
@RequestPart(required = false) Map<String, String> json, Model model) throws IOException {
if (file != null && !file.isEmpty()) {
byte[] content = file.get(0).getBytes();
assertThat(file.get(1).getBytes()).isEqualTo(content);
model.addAttribute("fileContent", content);
}
if (json != null) {
model.addAttribute("jsonContent", json);
}
return "redirect:/index";
}
@RequestMapping(value = "/optionalfile", method = RequestMethod.POST)
public String processOptionalFile(@RequestParam Optional<MultipartFile> file,
@RequestPart Map<String, String> json, Model model) throws IOException {
if (file.isPresent()) {
model.addAttribute("fileContent", file.get().getBytes());
}
model.addAttribute("jsonContent", json);
return "redirect:/index";
}
@RequestMapping(value = "/optionalfilearray", method = RequestMethod.POST)
public String processOptionalFileArray(@RequestParam Optional<MultipartFile[]> file,
@RequestPart Map<String, String> json, Model model) throws IOException {
if (file.isPresent()) {
byte[] content = file.get()[0].getBytes();
assertThat(file.get()[1].getBytes()).isEqualTo(content);
model.addAttribute("fileContent", content);
}
model.addAttribute("jsonContent", json);
return "redirect:/index";
}
@RequestMapping(value = "/optionalfilelist", method = RequestMethod.POST)
public String processOptionalFileList(@RequestParam Optional<List<MultipartFile>> file,
@RequestPart Map<String, String> json, Model model) throws IOException {
if (file.isPresent()) {
byte[] content = file.get().get(0).getBytes();
assertThat(file.get().get(1).getBytes()).isEqualTo(content);
model.addAttribute("fileContent", content);
}
model.addAttribute("jsonContent", json);
return "redirect:/index";
}
@RequestMapping(value = "/part", method = RequestMethod.POST)
public String processPart(@RequestParam Part part,
@RequestPart Map<String, String> json, Model model) throws IOException {
model.addAttribute("fileContent", part.getInputStream());
model.addAttribute("jsonContent", json);
return "redirect:/index";
}
@RequestMapping(value = "/json", method = RequestMethod.POST)
public String processMultipart(@RequestPart Map<String, String> json, Model model) {
model.addAttribute("json", json);
return "redirect:/index";
}
}
private static class RequestWrappingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
request = new HttpServletRequestWrapper(request);
filterChain.doFilter(request, response);
}
}
}

View File

@ -0,0 +1,71 @@
/*
* 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.samples.client.standalone;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.ReactiveReturnTypeTests}.
*
* @author Rossen Stoyanchev
*/
public class ReactiveReturnTypeTests {
@Test
public void sseWithFlux() {
WebTestClient testClient =
MockMvcTestClient.bindToController(new ReactiveController()).build();
Flux<String> bodyFlux = testClient.get().uri("/spr16869")
.exchange()
.expectStatus().isOk()
.expectHeader().contentTypeCompatibleWith(TEXT_EVENT_STREAM)
.returnResult(String.class)
.getResponseBody();
StepVerifier.create(bodyFlux)
.expectNext("event0")
.expectNext("event1")
.expectNext("event2")
.verifyComplete();
}
@RestController
static class ReactiveController {
@GetMapping(path = "/spr16869", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
Flux<String> sseFlux() {
return Flux.interval(Duration.ofSeconds(1)).take(3)
.map(aLong -> String.format("event%d", aLong));
}
}
}

View File

@ -0,0 +1,163 @@
/*
* 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.samples.client.standalone;
import javax.validation.Valid;
import org.junit.jupiter.api.Test;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.flash;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.RedirectTests}.
*
* @author Rossen Stoyanchev
*/
public class RedirectTests {
private final WebTestClient testClient =
MockMvcTestClient.bindToController(new PersonController()).build();
@Test
public void save() throws Exception {
EntityExchangeResult<Void> exchangeResult =
testClient.post().uri("/persons?name=Andy")
.exchange()
.expectStatus().isFound()
.expectHeader().location("/persons/Joe")
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().size(1))
.andExpect(model().attributeExists("name"))
.andExpect(flash().attributeCount(1))
.andExpect(flash().attribute("message", "success!"));
}
@Test
public void saveSpecial() throws Exception {
EntityExchangeResult<Void> result =
testClient.post().uri("/people?name=Andy")
.exchange()
.expectStatus().isFound()
.expectHeader().location("/persons/Joe")
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(result)
.andExpect(model().size(1))
.andExpect(model().attributeExists("name"))
.andExpect(flash().attributeCount(1))
.andExpect(flash().attribute("message", "success!"));
}
@Test
public void saveWithErrors() throws Exception {
EntityExchangeResult<Void> result =
testClient.post().uri("/persons").exchange().expectStatus().isOk().expectBody().isEmpty();
MockMvcTestClient.resultActionsFor(result)
.andExpect(forwardedUrl("persons/add"))
.andExpect(model().size(1))
.andExpect(model().attributeExists("person"))
.andExpect(flash().attributeCount(0));
}
@Test
public void saveSpecialWithErrors() throws Exception {
EntityExchangeResult<Void> result =
testClient.post().uri("/people").exchange().expectStatus().isOk().expectBody().isEmpty();
MockMvcTestClient.resultActionsFor(result)
.andExpect(forwardedUrl("persons/add"))
.andExpect(model().size(1))
.andExpect(model().attributeExists("person"))
.andExpect(flash().attributeCount(0));
}
@Test
public void getPerson() throws Exception {
EntityExchangeResult<Void> result =
MockMvcTestClient.bindToController(new PersonController())
.defaultRequest(get("/").flashAttr("message", "success!"))
.build()
.get().uri("/persons/Joe")
.exchange()
.expectStatus().isOk()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(result)
.andDo(MockMvcResultHandlers.print())
.andExpect(forwardedUrl("persons/index"))
.andExpect(model().size(2))
.andExpect(model().attribute("person", new Person("Joe")))
.andExpect(model().attribute("message", "success!"))
.andExpect(flash().attributeCount(0));
}
@Controller
private static class PersonController {
@GetMapping("/persons/{name}")
public String getPerson(@PathVariable String name, Model model) {
model.addAttribute(new Person(name));
return "persons/index";
}
@PostMapping("/persons")
public String save(@Valid Person person, Errors errors, RedirectAttributes redirectAttrs) {
if (errors.hasErrors()) {
return "persons/add";
}
redirectAttrs.addAttribute("name", "Joe");
redirectAttrs.addFlashAttribute("message", "success!");
return "redirect:/persons/{name}";
}
@PostMapping("/people")
public Object saveSpecial(@Valid Person person, Errors errors, RedirectAttributes redirectAttrs) {
if (errors.hasErrors()) {
return "persons/add";
}
redirectAttrs.addAttribute("name", "Joe");
redirectAttrs.addFlashAttribute("message", "success!");
return new StringBuilder("redirect:").append("/persons").append("/{name}");
}
}
}

View File

@ -0,0 +1,62 @@
/*
* 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.samples.client.standalone;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.RequestParameterTests}.
*
* @author Rossen Stoyanchev
*/
public class RequestParameterTests {
@Test
public void queryParameter() {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController()).build();
client.get().uri("/search?name=George")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody().jsonPath("$.name", "George");
}
@Controller
private class PersonController {
@RequestMapping(value="/search")
@ResponseBody
public Person get(@RequestParam String name) {
Person person = new Person(name);
return person;
}
}
}

View File

@ -0,0 +1,90 @@
/*
* 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.samples.client.standalone;
import javax.validation.constraints.NotNull;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import static org.hamcrest.Matchers.equalTo;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.ResponseBodyTests}.
*
* @author Rossen Stoyanchev
*/
public class ResponseBodyTests {
@Test
void json() {
MockMvcTestClient.bindToController(new PersonController()).build()
.get()
.uri("/person/Lee")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody()
.jsonPath("$.name").isEqualTo("Lee")
.jsonPath("$.age").isEqualTo(42)
.jsonPath("$.age").value(equalTo(42))
.jsonPath("$.age").value(equalTo(42.0f), Float.class);
}
@RestController
private static class PersonController {
@GetMapping("/person/{name}")
public Person get(@PathVariable String name) {
Person person = new Person(name);
person.setAge(42);
return person;
}
}
private static class Person {
@NotNull
private final String name;
private int age;
public Person(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
}

View File

@ -0,0 +1,183 @@
/*
* 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.samples.client.standalone;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.ui.Model;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.accept.FixedContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
import org.springframework.web.servlet.view.xml.MarshallingView;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasProperty;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.RequestParameterTests}.
*
* @author Rossen Stoyanchev
*/
class ViewResolutionTests {
@Test
void jspOnly() throws Exception {
WebTestClient testClient =
MockMvcTestClient.bindToController(new PersonController())
.viewResolvers(new InternalResourceViewResolver("/WEB-INF/", ".jsp"))
.build();
EntityExchangeResult<Void> result = testClient.get().uri("/person/Corea")
.exchange()
.expectStatus().isOk()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(result)
.andExpect(status().isOk())
.andExpect(model().size(1))
.andExpect(model().attributeExists("person"))
.andExpect(forwardedUrl("/WEB-INF/person/show.jsp"));
}
@Test
void jsonOnly() {
WebTestClient testClient =
MockMvcTestClient.bindToController(new PersonController())
.singleView(new MappingJackson2JsonView())
.build();
testClient.get().uri("/person/Corea")
.exchange()
.expectStatus().isOk()
.expectHeader().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)
.expectBody().jsonPath("$.person.name", "Corea");
}
@Test
void xmlOnly() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(Person.class);
WebTestClient testClient =
MockMvcTestClient.bindToController(new PersonController())
.singleView(new MarshallingView(marshaller))
.build();
testClient.get().uri("/person/Corea")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_XML)
.expectBody().xpath("/person/name/text()").isEqualTo("Corea");
}
@Test
void contentNegotiation() throws Exception {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(Person.class);
List<View> viewList = new ArrayList<>();
viewList.add(new MappingJackson2JsonView());
viewList.add(new MarshallingView(marshaller));
ContentNegotiationManager manager = new ContentNegotiationManager(
new HeaderContentNegotiationStrategy(), new FixedContentNegotiationStrategy(MediaType.TEXT_HTML));
ContentNegotiatingViewResolver cnViewResolver = new ContentNegotiatingViewResolver();
cnViewResolver.setDefaultViews(viewList);
cnViewResolver.setContentNegotiationManager(manager);
cnViewResolver.afterPropertiesSet();
WebTestClient testClient =
MockMvcTestClient.bindToController(new PersonController())
.viewResolvers(cnViewResolver, new InternalResourceViewResolver())
.build();
EntityExchangeResult<Void> result = testClient.get().uri("/person/Corea")
.exchange()
.expectStatus().isOk()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(result)
.andExpect(model().size(1))
.andExpect(model().attributeExists("person"))
.andExpect(forwardedUrl("person/show"));
testClient.get().uri("/person/Corea")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectHeader().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)
.expectBody().jsonPath("$.person.name", "Corea");
testClient.get().uri("/person/Corea")
.accept(MediaType.APPLICATION_XML)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_XML)
.expectBody().xpath("/person/name/text()").isEqualTo("Corea");
}
@Test
void defaultViewResolver() throws Exception {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController()).build();
EntityExchangeResult<Void> result = client.get().uri("/person/Corea")
.exchange()
.expectStatus().isOk()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(result)
.andExpect(model().attribute("person", hasProperty("name", equalTo("Corea"))))
.andExpect(forwardedUrl("person/show")); // InternalResourceViewResolver
}
@Controller
private static class PersonController {
@GetMapping("/person/{name}")
String show(@PathVariable String name, Model model) {
Person person = new Person(name);
model.addAttribute(person);
return "person/show";
}
}
}

View File

@ -0,0 +1,84 @@
/*
* 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.samples.client.standalone.resulthandlers;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resulthandlers.PrintingResultHandlerSmokeTests}.
*
* @author Rossen Stoyanchev
*/
@Disabled
public class PrintingResultHandlerSmokeTests {
private final WebTestClient testClient =
MockMvcTestClient.bindToController(new SimpleController()).build();
// Not intended to be executed with the build.
// Comment out class-level @Disabled to see the output.
@Test
public void printViaConsumer() {
testClient.post().uri("/")
.contentType(MediaType.TEXT_PLAIN)
.bodyValue("Hello Request".getBytes(StandardCharsets.UTF_8))
.exchange()
.expectStatus().isOk()
.expectBody(String.class)
.consumeWith(System.out::println);
}
@Test
public void returnResultAndPrint() {
EntityExchangeResult<String> result = testClient.post().uri("/")
.contentType(MediaType.TEXT_PLAIN)
.bodyValue("Hello Request".getBytes(StandardCharsets.UTF_8))
.exchange()
.expectStatus().isOk()
.expectBody(String.class)
.returnResult();
System.out.println(result);
}
@RestController
private static class SimpleController {
@PostMapping("/")
public String hello(HttpServletResponse response) {
response.addCookie(new Cookie("enigma", "42"));
return "Hello Response";
}
}
}

View File

@ -0,0 +1,145 @@
/*
* 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.samples.client.standalone.resultmatches;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.ContentAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class ContentAssertionTests {
private final WebTestClient testClient =
MockMvcTestClient.bindToController(new SimpleController()).build();
@Test
public void testContentType() {
testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.valueOf("text/plain;charset=ISO-8859-1"))
.expectHeader().contentType("text/plain;charset=ISO-8859-1")
.expectHeader().contentTypeCompatibleWith("text/plain")
.expectHeader().contentTypeCompatibleWith(MediaType.TEXT_PLAIN);
testClient.get().uri("/handleUtf8")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.valueOf("text/plain;charset=UTF-8"))
.expectHeader().contentType("text/plain;charset=UTF-8")
.expectHeader().contentTypeCompatibleWith("text/plain")
.expectHeader().contentTypeCompatibleWith(MediaType.TEXT_PLAIN);
}
@Test
public void testContentAsString() {
testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello world!");
testClient.get().uri("/handleUtf8").accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01");
// Hamcrest matchers...
testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isOk()
.expectBody(String.class).value(equalTo("Hello world!"));
testClient.get().uri("/handleUtf8")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).value(equalTo("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01"));
}
@Test
public void testContentAsBytes() {
testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isOk()
.expectBody(byte[].class).isEqualTo(
"Hello world!".getBytes(StandardCharsets.ISO_8859_1));
testClient.get().uri("/handleUtf8")
.exchange()
.expectStatus().isOk()
.expectBody(byte[].class).isEqualTo(
"\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01".getBytes(StandardCharsets.UTF_8));
}
@Test
public void testContentStringMatcher() {
testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isOk()
.expectBody(String.class).value(containsString("world"));
}
@Test
public void testCharacterEncoding() {
testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType("text/plain;charset=ISO-8859-1")
.expectBody(String.class).value(containsString("world"));
testClient.get().uri("/handleUtf8")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType("text/plain;charset=UTF-8")
.expectBody(byte[].class)
.isEqualTo("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01".getBytes(StandardCharsets.UTF_8));
}
@Controller
private static class SimpleController {
@RequestMapping(value="/handle", produces="text/plain")
@ResponseBody
public String handle() {
return "Hello world!";
}
@RequestMapping(value="/handleUtf8", produces="text/plain;charset=UTF-8")
@ResponseBody
public String handleWithCharset() {
return "\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01"; // "Hello world! (Japanese)
}
}
}

View File

@ -0,0 +1,120 @@
/*
* 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.samples.client.standalone.resultmatches;
import java.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.CookieAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class CookieAssertionTests {
private static final String COOKIE_NAME = CookieLocaleResolver.DEFAULT_COOKIE_NAME;
private WebTestClient client;
@BeforeEach
public void setup() {
CookieLocaleResolver localeResolver = new CookieLocaleResolver();
localeResolver.setCookieDomain("domain");
localeResolver.setCookieHttpOnly(true);
client = MockMvcTestClient.bindToController(new SimpleController())
.interceptors(new LocaleChangeInterceptor())
.localeResolver(localeResolver)
.alwaysExpect(status().isOk())
.configureClient()
.baseUrl("/?locale=en_US")
.build();
}
@Test
public void testExists() {
client.get().uri("/").exchange().expectCookie().exists(COOKIE_NAME);
}
@Test
public void testNotExists() {
client.get().uri("/").exchange().expectCookie().doesNotExist("unknownCookie");
}
@Test
public void testEqualTo() {
client.get().uri("/").exchange().expectCookie().valueEquals(COOKIE_NAME, "en-US");
client.get().uri("/").exchange().expectCookie().value(COOKIE_NAME, equalTo("en-US"));
}
@Test
public void testMatcher() {
client.get().uri("/").exchange().expectCookie().value(COOKIE_NAME, startsWith("en-US"));
}
@Test
public void testMaxAge() {
client.get().uri("/").exchange().expectCookie().maxAge(COOKIE_NAME, Duration.ofSeconds(-1));
}
@Test
public void testDomain() {
client.get().uri("/").exchange().expectCookie().domain(COOKIE_NAME, "domain");
}
@Test
public void testPath() {
client.get().uri("/").exchange().expectCookie().path(COOKIE_NAME, "/");
}
@Test
public void testSecured() {
client.get().uri("/").exchange().expectCookie().secure(COOKIE_NAME, false);
}
@Test
public void testHttpOnly() {
client.get().uri("/").exchange().expectCookie().httpOnly(COOKIE_NAME, true);
}
@Controller
private static class SimpleController {
@RequestMapping("/")
public String home() {
return "home";
}
}
}

View File

@ -0,0 +1,103 @@
/*
* 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.samples.client.standalone.resultmatches;
import java.net.URL;
import org.junit.jupiter.api.Test;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.hamcrest.Matchers.closeTo;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.flash;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.FlashAttributeAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class FlashAttributeAssertionTests {
private final WebTestClient client =
MockMvcTestClient.bindToController(new PersonController())
.alwaysExpect(status().isFound())
.alwaysExpect(flash().attributeCount(3))
.build();
@Test
void attributeCountWithWrongCount() {
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> performRequest().andExpect(flash().attributeCount(1)))
.withMessage("FlashMap size expected:<1> but was:<3>");
}
@Test
void attributeExists() throws Exception {
performRequest().andExpect(flash().attributeExists("one", "two", "three"));
}
@Test
void attributeEqualTo() throws Exception {
performRequest()
.andExpect(flash().attribute("one", "1"))
.andExpect(flash().attribute("two", 2.222))
.andExpect(flash().attribute("three", new URL("https://example.com")));
}
@Test
void attributeMatchers() throws Exception {
performRequest()
.andExpect(flash().attribute("one", containsString("1")))
.andExpect(flash().attribute("two", closeTo(2, 0.5)))
.andExpect(flash().attribute("three", notNullValue()))
.andExpect(flash().attribute("one", equalTo("1")))
.andExpect(flash().attribute("two", equalTo(2.222)))
.andExpect(flash().attribute("three", equalTo(new URL("https://example.com"))));
}
private ResultActions performRequest() {
EntityExchangeResult<Void> result = client.post().uri("/persons").exchange().expectBody().isEmpty();
return MockMvcTestClient.resultActionsFor(result);
}
@Controller
private static class PersonController {
@PostMapping("/persons")
String save(RedirectAttributes redirectAttrs) throws Exception {
redirectAttrs.addFlashAttribute("one", "1");
redirectAttrs.addFlashAttribute("two", 2.222);
redirectAttrs.addFlashAttribute("three", new URL("https://example.com"));
return "redirect:/person/1";
}
}
}

View File

@ -0,0 +1,106 @@
/*
* 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.samples.client.standalone.resultmatches;
import java.lang.reflect.Method;
import org.junit.jupiter.api.Test;
import org.springframework.http.ResponseEntity;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.handler;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.on;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.HandlerAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class HandlerAssertionTests {
private final WebTestClient client =
MockMvcTestClient.bindToController(new SimpleController())
.alwaysExpect(status().isOk())
.build();
@Test
public void handlerType() throws Exception {
performRequest().andExpect(handler().handlerType(SimpleController.class));
}
@Test
public void methodCallOnNonMock() {
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> performRequest().andExpect(handler().methodCall("bogus")))
.withMessageContaining("The supplied object [bogus] is not an instance of")
.withMessageContaining(MvcUriComponentsBuilder.MethodInvocationInfo.class.getName())
.withMessageContaining("Ensure that you invoke the handler method via MvcUriComponentsBuilder.on()");
}
@Test
public void methodCall() throws Exception {
performRequest().andExpect(handler().methodCall(on(SimpleController.class).handle()));
}
@Test
public void methodName() throws Exception {
performRequest().andExpect(handler().methodName("handle"));
}
@Test
public void methodNameMatchers() throws Exception {
performRequest()
.andExpect(handler().methodName(equalTo("handle")))
.andExpect(handler().methodName(is(not("save"))));
}
@Test
public void method() throws Exception {
Method method = SimpleController.class.getMethod("handle");
performRequest().andExpect(handler().method(method));
}
private ResultActions performRequest() {
EntityExchangeResult<Void> result = client.get().uri("/").exchange().expectBody().isEmpty();
return MockMvcTestClient.resultActionsFor(result);
}
@RestController
static class SimpleController {
@RequestMapping("/")
public ResponseEntity<Void> handle() {
return ResponseEntity.ok().build();
}
}
}

View File

@ -0,0 +1,272 @@
/*
* 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.samples.client.standalone.resultmatches;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.function.Consumer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.WebRequest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.fail;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.http.HttpHeaders.IF_MODIFIED_SINCE;
import static org.springframework.http.HttpHeaders.LAST_MODIFIED;
import static org.springframework.http.HttpHeaders.VARY;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.HeaderAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class HeaderAssertionTests {
private static final String ERROR_MESSAGE = "Should have thrown an AssertionError";
private String now;
private String minuteAgo;
private WebTestClient testClient;
private final long currentTime = System.currentTimeMillis();
private SimpleDateFormat dateFormat;
@BeforeEach
public void setup() {
this.dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
this.dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
this.now = dateFormat.format(new Date(this.currentTime));
this.minuteAgo = dateFormat.format(new Date(this.currentTime - (1000 * 60)));
PersonController controller = new PersonController();
controller.setStubTimestamp(this.currentTime);
this.testClient = MockMvcTestClient.bindToController(controller).build();
}
@Test
public void stringWithCorrectResponseHeaderValue() {
testClient.get().uri("/persons/1").header(IF_MODIFIED_SINCE, minuteAgo)
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals(LAST_MODIFIED, now);
}
@Test
public void stringWithMatcherAndCorrectResponseHeaderValue() {
testClient.get().uri("/persons/1").header(IF_MODIFIED_SINCE, minuteAgo)
.exchange()
.expectStatus().isOk()
.expectHeader().value(LAST_MODIFIED, equalTo(now));
}
@Test
public void multiStringHeaderValue() {
testClient.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals(VARY, "foo", "bar");
}
@Test
public void multiStringHeaderValueWithMatchers() {
testClient.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectHeader().values(VARY, hasItems(containsString("foo"), startsWith("bar")));
}
@Test
public void dateValueWithCorrectResponseHeaderValue() {
testClient.get().uri("/persons/1")
.header(IF_MODIFIED_SINCE, minuteAgo)
.exchange()
.expectStatus().isOk()
.expectHeader().valueEqualsDate(LAST_MODIFIED, this.currentTime);
}
@Test
public void longValueWithCorrectResponseHeaderValue() {
testClient.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("X-Rate-Limiting", 42);
}
@Test
public void stringWithMissingResponseHeader() {
testClient.get().uri("/persons/1")
.header(IF_MODIFIED_SINCE, now)
.exchange()
.expectStatus().isNotModified()
.expectHeader().valueEquals("X-Custom-Header");
}
@Test
public void stringWithMatcherAndMissingResponseHeader() {
testClient.get().uri("/persons/1").header(IF_MODIFIED_SINCE, now)
.exchange()
.expectStatus().isNotModified()
.expectHeader().value("X-Custom-Header", nullValue());
}
@Test
public void longValueWithMissingResponseHeader() {
try {
testClient.get().uri("/persons/1").header(IF_MODIFIED_SINCE, now)
.exchange()
.expectStatus().isNotModified()
.expectHeader().valueEquals("X-Custom-Header", 99L);
fail(ERROR_MESSAGE);
}
catch (AssertionError err) {
if (ERROR_MESSAGE.equals(err.getMessage())) {
throw err;
}
assertThat(err.getMessage()).startsWith("Response does not contain header 'X-Custom-Header'");
}
}
@Test
public void exists() {
testClient.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectHeader().exists(LAST_MODIFIED);
}
@Test
public void existsFail() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
testClient.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectHeader().exists("X-Custom-Header"));
}
@Test
public void doesNotExist() {
testClient.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectHeader().doesNotExist("X-Custom-Header");
}
@Test
public void doesNotExistFail() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
testClient.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectHeader().doesNotExist(LAST_MODIFIED));
}
@Test
public void longValueWithIncorrectResponseHeaderValue() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
testClient.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("X-Rate-Limiting", 1));
}
@Test
public void stringWithMatcherAndIncorrectResponseHeaderValue() {
long secondLater = this.currentTime + 1000;
String expected = this.dateFormat.format(new Date(secondLater));
assertIncorrectResponseHeader(spec -> spec.expectHeader().valueEquals(LAST_MODIFIED, expected), expected);
assertIncorrectResponseHeader(spec -> spec.expectHeader().value(LAST_MODIFIED, equalTo(expected)), expected);
// Comparison by date uses HttpHeaders to format the date in the error message.
HttpHeaders headers = new HttpHeaders();
headers.setDate("expected", secondLater);
assertIncorrectResponseHeader(spec -> spec.expectHeader().valueEqualsDate(LAST_MODIFIED, secondLater), expected);
}
private void assertIncorrectResponseHeader(Consumer<WebTestClient.ResponseSpec> assertions, String expected) {
try {
WebTestClient.ResponseSpec spec = testClient.get().uri("/persons/1")
.header(IF_MODIFIED_SINCE, minuteAgo)
.exchange()
.expectStatus().isOk();
assertions.accept(spec);
fail(ERROR_MESSAGE);
}
catch (AssertionError err) {
if (ERROR_MESSAGE.equals(err.getMessage())) {
throw err;
}
assertMessageContains(err, "Response header '" + LAST_MODIFIED + "'");
assertMessageContains(err, expected);
assertMessageContains(err, this.now);
}
}
private void assertMessageContains(AssertionError error, String expected) {
assertThat(error.getMessage().contains(expected))
.as("Failure message should contain [" + expected + "], actual is [" + error.getMessage() + "]")
.isTrue();
}
@Controller
private static class PersonController {
private long timestamp;
public void setStubTimestamp(long timestamp) {
this.timestamp = timestamp;
}
@RequestMapping("/persons/{id}")
public ResponseEntity<Person> showEntity(@PathVariable long id, WebRequest request) {
return ResponseEntity
.ok()
.lastModified(this.timestamp)
.header("X-Rate-Limiting", "42")
.header("Vary", "foo", "bar")
.body(new Person("Jason"));
}
}
}

View File

@ -0,0 +1,152 @@
/*
* 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.samples.client.standalone.resultmatches;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.in;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.JsonPathAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class JsonPathAssertionTests {
private final WebTestClient client =
MockMvcTestClient.bindToController(new MusicController())
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType(MediaType.APPLICATION_JSON))
.configureClient()
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build();
@Test
public void exists() {
String composerByName = "$.composers[?(@.name == '%s')]";
String performerByName = "$.performers[?(@.name == '%s')]";
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath(composerByName, "Johann Sebastian Bach").exists()
.jsonPath(composerByName, "Johannes Brahms").exists()
.jsonPath(composerByName, "Edvard Grieg").exists()
.jsonPath(composerByName, "Robert Schumann").exists()
.jsonPath(performerByName, "Vladimir Ashkenazy").exists()
.jsonPath(performerByName, "Yehudi Menuhin").exists()
.jsonPath("$.composers[0]").exists()
.jsonPath("$.composers[1]").exists()
.jsonPath("$.composers[2]").exists()
.jsonPath("$.composers[3]").exists();
}
@Test
public void doesNotExist() {
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath("$.composers[?(@.name == 'Edvard Grieeeeeeg')]").doesNotExist()
.jsonPath("$.composers[?(@.name == 'Robert Schuuuuuuman')]").doesNotExist()
.jsonPath("$.composers[4]").doesNotExist();
}
@Test
public void equality() {
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath("$.composers[0].name").isEqualTo("Johann Sebastian Bach")
.jsonPath("$.performers[1].name").isEqualTo("Yehudi Menuhin");
// Hamcrest matchers...
client.get().uri("/music/people")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody()
.jsonPath("$.composers[0].name").value(equalTo("Johann Sebastian Bach"))
.jsonPath("$.performers[1].name").value(equalTo("Yehudi Menuhin"));
}
@Test
public void hamcrestMatcher() {
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath("$.composers[0].name").value(startsWith("Johann"))
.jsonPath("$.performers[0].name").value(endsWith("Ashkenazy"))
.jsonPath("$.performers[1].name").value(containsString("di Me"))
.jsonPath("$.composers[1].name").value(is(in(Arrays.asList("Johann Sebastian Bach", "Johannes Brahms"))));
}
@Test
public void hamcrestMatcherWithParameterizedJsonPath() {
String composerName = "$.composers[%s].name";
String performerName = "$.performers[%s].name";
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath(composerName, 0).value(startsWith("Johann"))
.jsonPath(performerName, 0).value(endsWith("Ashkenazy"))
.jsonPath(performerName, 1).value(containsString("di Me"))
.jsonPath(composerName, 1).value(is(in(Arrays.asList("Johann Sebastian Bach", "Johannes Brahms"))));
}
@RestController
private class MusicController {
@RequestMapping("/music/people")
public MultiValueMap<String, Person> get() {
MultiValueMap<String, Person> map = new LinkedMultiValueMap<>();
map.add("composers", new Person("Johann Sebastian Bach"));
map.add("composers", new Person("Johannes Brahms"));
map.add("composers", new Person("Edvard Grieg"));
map.add("composers", new Person("Robert Schumann"));
map.add("performers", new Person("Vladimir Ashkenazy"));
map.add("performers", new Person("Yehudi Menuhin"));
return map;
}
}
}

View File

@ -0,0 +1,135 @@
/*
* 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.samples.client.standalone.resultmatches;
import javax.validation.Valid;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.ModelAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class ModelAssertionTests {
private final WebTestClient client =
MockMvcTestClient.bindToController(new SampleController("a string value", 3, new Person("a name")))
.controllerAdvice(new ModelAttributeAdvice())
.alwaysExpect(status().isOk())
.build();
@Test
void attributeEqualTo() throws Exception {
performRequest(HttpMethod.GET, "/")
.andExpect(model().attribute("integer", 3))
.andExpect(model().attribute("string", "a string value"))
.andExpect(model().attribute("integer", equalTo(3))) // Hamcrest...
.andExpect(model().attribute("string", equalTo("a string value")))
.andExpect(model().attribute("globalAttrName", equalTo("Global Attribute Value")));
}
@Test
void attributeExists() throws Exception {
performRequest(HttpMethod.GET, "/")
.andExpect(model().attributeExists("integer", "string", "person"))
.andExpect(model().attribute("integer", notNullValue())) // Hamcrest...
.andExpect(model().attribute("INTEGER", nullValue()));
}
@Test
void attributeHamcrestMatchers() throws Exception {
performRequest(HttpMethod.GET, "/")
.andExpect(model().attribute("integer", equalTo(3)))
.andExpect(model().attribute("string", allOf(startsWith("a string"), endsWith("value"))))
.andExpect(model().attribute("person", hasProperty("name", equalTo("a name"))));
}
@Test
void hasErrors() throws Exception {
performRequest(HttpMethod.POST, "/persons").andExpect(model().attributeHasErrors("person"));
}
@Test
void hasNoErrors() throws Exception {
performRequest(HttpMethod.GET, "/").andExpect(model().hasNoErrors());
}
private ResultActions performRequest(HttpMethod method, String uri) {
EntityExchangeResult<Void> result = client.method(method).uri(uri).exchange().expectBody().isEmpty();
return MockMvcTestClient.resultActionsFor(result);
}
@Controller
private static class SampleController {
private final Object[] values;
SampleController(Object... values) {
this.values = values;
}
@RequestMapping("/")
String handle(Model model) {
for (Object value : this.values) {
model.addAttribute(value);
}
return "view";
}
@PostMapping("/persons")
String create(@Valid Person person, BindingResult result, Model model) {
return "view";
}
}
@ControllerAdvice
private static class ModelAttributeAdvice {
@ModelAttribute("globalAttrName")
String getAttribute() {
return "Global Attribute Value";
}
}
}

View File

@ -0,0 +1,92 @@
/*
* 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.samples.client.standalone.resultmatches;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.HandlerMapping;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.RequestAttributeAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class RequestAttributeAssertionTests {
private final WebTestClient mainServletClient =
MockMvcTestClient.bindToController(new SimpleController())
.defaultRequest(get("/").servletPath("/main"))
.build();
private final WebTestClient client =
MockMvcTestClient.bindToController(new SimpleController()).build();
@Test
void requestAttributeEqualTo() throws Exception {
performRequest(mainServletClient, "/main/1")
.andExpect(request().attribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/{id}"))
.andExpect(request().attribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/1"));
}
@Test
void requestAttributeMatcher() throws Exception {
String producibleMediaTypes = HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE;
performRequest(client, "/1")
.andExpect(request().attribute(producibleMediaTypes, hasItem(MediaType.APPLICATION_JSON)))
.andExpect(request().attribute(producibleMediaTypes, not(hasItem(MediaType.APPLICATION_XML))));
performRequest(mainServletClient, "/main/1")
.andExpect(request().attribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, equalTo("/{id}")))
.andExpect(request().attribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, equalTo("/1")));
}
private ResultActions performRequest(WebTestClient client, String uri) {
EntityExchangeResult<Void> result = client.get().uri(uri)
.exchange()
.expectStatus().isOk()
.expectBody().isEmpty();
return MockMvcTestClient.resultActionsFor(result);
}
@Controller
private static class SimpleController {
@GetMapping(path="/{id}", produces="application/json")
String show() {
return "view";
}
}
}

View File

@ -0,0 +1,106 @@
/*
* 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.samples.client.standalone.resultmatches;
import java.util.Locale;
import org.junit.jupiter.api.Test;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.SessionAttributeAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class SessionAttributeAssertionTests {
private final WebTestClient client =
MockMvcTestClient.bindToController(new SimpleController())
.alwaysExpect(status().isOk())
.build();
@Test
void sessionAttributeEqualTo() throws Exception {
performRequest().andExpect(request().sessionAttribute("locale", Locale.UK));
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> performRequest().andExpect(request().sessionAttribute("locale", Locale.US)))
.withMessage("Session attribute 'locale' expected:<en_US> but was:<en_GB>");
}
@Test
void sessionAttributeMatcher() throws Exception {
performRequest()
.andExpect(request().sessionAttribute("bogus", is(nullValue())))
.andExpect(request().sessionAttribute("locale", is(notNullValue())))
.andExpect(request().sessionAttribute("locale", equalTo(Locale.UK)));
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> performRequest().andExpect(request().sessionAttribute("bogus", is(notNullValue()))))
.withMessageContaining("null");
}
@Test
void sessionAttributeDoesNotExist() throws Exception {
performRequest().andExpect(request().sessionAttributeDoesNotExist("bogus", "enigma"));
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> performRequest().andExpect(request().sessionAttributeDoesNotExist("locale")))
.withMessage("Session attribute 'locale' exists");
}
private ResultActions performRequest() {
EntityExchangeResult<Void> result = client.post().uri("/").exchange().expectBody().isEmpty();
return MockMvcTestClient.resultActionsFor(result);
}
@Controller
@SessionAttributes("locale")
private static class SimpleController {
@ModelAttribute
void populate(Model model) {
model.addAttribute("locale", Locale.UK);
}
@RequestMapping("/")
String handle() {
return "view";
}
}
}

View File

@ -0,0 +1,106 @@
/*
* 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.samples.client.standalone.resultmatches;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.junit.jupiter.api.Test;
import org.springframework.core.annotation.AliasFor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.NOT_IMPLEMENTED;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.StatusAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class StatusAssertionTests {
private final WebTestClient testClient =
MockMvcTestClient.bindToController(new StatusController()).build();
@Test
public void testStatusInt() {
testClient.get().uri("/created").exchange().expectStatus().isEqualTo(201);
testClient.get().uri("/createdWithComposedAnnotation").exchange().expectStatus().isEqualTo(201);
testClient.get().uri("/badRequest").exchange().expectStatus().isEqualTo(400);
}
@Test
public void testHttpStatus() {
testClient.get().uri("/created").exchange().expectStatus().isCreated();
testClient.get().uri("/createdWithComposedAnnotation").exchange().expectStatus().isCreated();
testClient.get().uri("/badRequest").exchange().expectStatus().isBadRequest();
}
@Test
public void testMatcher() {
testClient.get().uri("/badRequest").exchange().expectStatus().value(equalTo(400));
}
@RequestMapping
@ResponseStatus
@Retention(RetentionPolicy.RUNTIME)
@interface Get {
@AliasFor(annotation = RequestMapping.class, attribute = "path")
String[] path() default {};
@AliasFor(annotation = ResponseStatus.class, attribute = "code")
HttpStatus status() default INTERNAL_SERVER_ERROR;
}
@Controller
private static class StatusController {
@RequestMapping("/created")
@ResponseStatus(CREATED)
public @ResponseBody void created(){
}
@Get(path = "/createdWithComposedAnnotation", status = CREATED)
public @ResponseBody void createdWithComposedAnnotation() {
}
@RequestMapping("/badRequest")
@ResponseStatus(code = BAD_REQUEST, reason = "Expired token")
public @ResponseBody void badRequest(){
}
@RequestMapping("/notImplemented")
@ResponseStatus(NOT_IMPLEMENTED)
public @ResponseBody void notImplemented(){
}
}
}

View File

@ -0,0 +1,90 @@
/*
* 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.samples.client.standalone.resultmatches;
import org.junit.jupiter.api.Test;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrlPattern;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.UrlAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class UrlAssertionTests {
private final WebTestClient testClient =
MockMvcTestClient.bindToController(new SimpleController()).build();
@Test
public void testRedirect() {
testClient.get().uri("/persons")
.exchange()
.expectStatus().isFound()
.expectHeader().location("/persons/1");
}
@Test
public void testRedirectPattern() throws Exception {
EntityExchangeResult<Void> result =
testClient.get().uri("/persons").exchange().expectBody().isEmpty();
MockMvcTestClient.resultActionsFor(result)
.andExpect(redirectedUrlPattern("/persons/*"));
}
@Test
public void testForward() {
testClient.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("Forwarded-Url", "/home");
}
@Test
public void testForwardPattern() throws Exception {
EntityExchangeResult<Void> result =
testClient.get().uri("/").exchange().expectBody().isEmpty();
MockMvcTestClient.resultActionsFor(result)
.andExpect(forwardedUrlPattern("/ho?e"));
}
@Controller
private static class SimpleController {
@RequestMapping("/persons")
public String save() {
return "redirect:/persons/1";
}
@RequestMapping("/")
public String forward() {
return "forward:/home";
}
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.samples.client.standalone.resultmatches;
import org.junit.jupiter.api.Test;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.UrlAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class ViewNameAssertionTests {
private final WebTestClient client =
MockMvcTestClient.bindToController(new SimpleController())
.alwaysExpect(status().isOk())
.build();
@Test
public void testEqualTo() throws Exception {
MockMvcTestClient.resultActionsFor(performRequest())
.andExpect(view().name("mySpecialView"))
.andExpect(view().name(equalTo("mySpecialView")));
}
@Test
public void testHamcrestMatcher() throws Exception {
MockMvcTestClient.resultActionsFor(performRequest())
.andExpect(view().name(containsString("Special")));
}
private EntityExchangeResult<Void> performRequest() {
return client.get().uri("/").exchange().expectBody().isEmpty();
}
@Controller
private static class SimpleController {
@RequestMapping("/")
public String handle() {
return "mySpecialView";
}
}
}

View File

@ -0,0 +1,120 @@
/*
* 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.samples.client.standalone.resultmatches;
import java.util.Arrays;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.XmlContentAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class XmlContentAssertionTests {
private static final String PEOPLE_XML =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
"<people><composers>" +
"<composer><name>Johann Sebastian Bach</name><someBoolean>false</someBoolean><someDouble>21.0</someDouble></composer>" +
"<composer><name>Johannes Brahms</name><someBoolean>false</someBoolean><someDouble>0.0025</someDouble></composer>" +
"<composer><name>Edvard Grieg</name><someBoolean>false</someBoolean><someDouble>1.6035</someDouble></composer>" +
"<composer><name>Robert Schumann</name><someBoolean>false</someBoolean><someDouble>NaN</someDouble></composer>" +
"</composers></people>";
private final WebTestClient testClient =
MockMvcTestClient.bindToController(new MusicController())
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType(MediaType.parseMediaType("application/xml;charset=UTF-8")))
.configureClient()
.defaultHeader(HttpHeaders.ACCEPT, "application/xml;charset=UTF-8")
.build();
@Test
public void testXmlEqualTo() {
testClient.get().uri("/music/people")
.exchange()
.expectBody().xml(PEOPLE_XML);
}
@Test
public void testNodeHamcrestMatcher() {
testClient.get().uri("/music/people")
.exchange()
.expectBody().xpath("/people/composers/composer[1]").exists();
}
@Controller
private static class MusicController {
@RequestMapping(value="/music/people")
public @ResponseBody PeopleWrapper getPeople() {
List<Person> composers = Arrays.asList(
new Person("Johann Sebastian Bach").setSomeDouble(21),
new Person("Johannes Brahms").setSomeDouble(.0025),
new Person("Edvard Grieg").setSomeDouble(1.6035),
new Person("Robert Schumann").setSomeDouble(Double.NaN));
return new PeopleWrapper(composers);
}
}
@SuppressWarnings("unused")
@XmlRootElement(name="people")
@XmlAccessorType(XmlAccessType.FIELD)
private static class PeopleWrapper {
@XmlElementWrapper(name="composers")
@XmlElement(name="composer")
private List<Person> composers;
public PeopleWrapper() {
}
public PeopleWrapper(List<Person> composers) {
this.composers = composers;
}
public List<Person> getComposers() {
return this.composers;
}
}
}

View File

@ -0,0 +1,236 @@
/*
* 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.samples.client.standalone.resultmatches;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import static org.hamcrest.Matchers.closeTo;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.HEAD;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.XpathAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class XpathAssertionTests {
private static final Map<String, String> musicNamespace =
Collections.singletonMap("ns", "https://example.org/music/people");
private final WebTestClient testClient =
MockMvcTestClient.bindToController(new MusicController())
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType(MediaType.parseMediaType("application/xml;charset=UTF-8")))
.configureClient()
.defaultHeader(HttpHeaders.ACCEPT, "application/xml;charset=UTF-8")
.build();
@Test
public void testExists() {
String composer = "/ns:people/composers/composer[%s]";
String performer = "/ns:people/performers/performer[%s]";
testClient.get().uri("/music/people")
.exchange()
.expectBody()
.xpath(composer, musicNamespace, 1).exists()
.xpath(composer, musicNamespace, 2).exists()
.xpath(composer, musicNamespace, 3).exists()
.xpath(composer, musicNamespace, 4).exists()
.xpath(performer, musicNamespace, 1).exists()
.xpath(performer, musicNamespace, 2).exists()
.xpath(composer, musicNamespace, 1).string(notNullValue());
}
@Test
public void testDoesNotExist() {
String composer = "/ns:people/composers/composer[%s]";
String performer = "/ns:people/performers/performer[%s]";
testClient.get().uri("/music/people")
.exchange()
.expectBody()
.xpath(composer, musicNamespace, 0).doesNotExist()
.xpath(composer, musicNamespace, 5).doesNotExist()
.xpath(performer, musicNamespace, 0).doesNotExist()
.xpath(performer, musicNamespace, 3).doesNotExist();
}
@Test
public void testString() {
String composerName = "/ns:people/composers/composer[%s]/name";
String performerName = "/ns:people/performers/performer[%s]/name";
testClient.get().uri("/music/people")
.exchange()
.expectBody()
.xpath(composerName, musicNamespace, 1).isEqualTo("Johann Sebastian Bach")
.xpath(composerName, musicNamespace, 2).isEqualTo("Johannes Brahms")
.xpath(composerName, musicNamespace, 3).isEqualTo("Edvard Grieg")
.xpath(composerName, musicNamespace, 4).isEqualTo("Robert Schumann")
.xpath(performerName, musicNamespace, 1).isEqualTo("Vladimir Ashkenazy")
.xpath(performerName, musicNamespace, 2).isEqualTo("Yehudi Menuhin")
.xpath(composerName, musicNamespace, 1).string(equalTo("Johann Sebastian Bach")) // Hamcrest..
.xpath(composerName, musicNamespace, 1).string(startsWith("Johann"))
.xpath(composerName, musicNamespace, 1).string(notNullValue());
}
@Test
public void testNumber() {
String expression = "/ns:people/composers/composer[%s]/someDouble";
testClient.get().uri("/music/people")
.exchange()
.expectBody()
.xpath(expression, musicNamespace, 1).isEqualTo(21d)
.xpath(expression, musicNamespace, 2).isEqualTo(.0025)
.xpath(expression, musicNamespace, 3).isEqualTo(1.6035)
.xpath(expression, musicNamespace, 4).isEqualTo(Double.NaN)
.xpath(expression, musicNamespace, 1).number(equalTo(21d)) // Hamcrest..
.xpath(expression, musicNamespace, 3).number(closeTo(1.6, .01));
}
@Test
public void testBoolean() {
String expression = "/ns:people/performers/performer[%s]/someBoolean";
testClient.get().uri("/music/people")
.exchange()
.expectBody()
.xpath(expression, musicNamespace, 1).isEqualTo(false)
.xpath(expression, musicNamespace, 2).isEqualTo(true);
}
@Test
public void testNodeCount() {
testClient.get().uri("/music/people")
.exchange()
.expectBody()
.xpath("/ns:people/composers/composer", musicNamespace).nodeCount(4)
.xpath("/ns:people/performers/performer", musicNamespace).nodeCount(2)
.xpath("/ns:people/composers/composer", musicNamespace).nodeCount(equalTo(4)) // Hamcrest..
.xpath("/ns:people/performers/performer", musicNamespace).nodeCount(equalTo(2));
}
@Test
public void testFeedWithLinefeedChars() {
MockMvcTestClient.bindToController(new BlogFeedController()).build()
.get().uri("/blog.atom")
.accept(MediaType.APPLICATION_ATOM_XML)
.exchange()
.expectBody()
.xpath("//feed/title").isEqualTo("Test Feed")
.xpath("//feed/icon").isEqualTo("https://www.example.com/favicon.ico");
}
@Controller
private static class MusicController {
@RequestMapping(value = "/music/people")
public @ResponseBody
PeopleWrapper getPeople() {
List<Person> composers = Arrays.asList(
new Person("Johann Sebastian Bach").setSomeDouble(21),
new Person("Johannes Brahms").setSomeDouble(.0025),
new Person("Edvard Grieg").setSomeDouble(1.6035),
new Person("Robert Schumann").setSomeDouble(Double.NaN));
List<Person> performers = Arrays.asList(
new Person("Vladimir Ashkenazy").setSomeBoolean(false),
new Person("Yehudi Menuhin").setSomeBoolean(true));
return new PeopleWrapper(composers, performers);
}
}
@SuppressWarnings("unused")
@XmlRootElement(name = "people", namespace = "https://example.org/music/people")
@XmlAccessorType(XmlAccessType.FIELD)
private static class PeopleWrapper {
@XmlElementWrapper(name = "composers")
@XmlElement(name = "composer")
private List<Person> composers;
@XmlElementWrapper(name = "performers")
@XmlElement(name = "performer")
private List<Person> performers;
public PeopleWrapper() {
}
public PeopleWrapper(List<Person> composers, List<Person> performers) {
this.composers = composers;
this.performers = performers;
}
public List<Person> getComposers() {
return this.composers;
}
public List<Person> getPerformers() {
return this.performers;
}
}
@Controller
public class BlogFeedController {
@RequestMapping(value = "/blog.atom", method = {GET, HEAD})
@ResponseBody
public String listPublishedPosts() {
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
+ "<feed xmlns=\"http://www.w3.org/2005/Atom\">\r\n"
+ " <title>Test Feed</title>\r\n"
+ " <icon>https://www.example.com/favicon.ico</icon>\r\n"
+ "</feed>\r\n\r\n";
}
}
}

View File

@ -29,7 +29,7 @@ public class PersonController {
private final PersonDao personDao;
PersonController(PersonDao personDao) {
public PersonController(PersonDao personDao) {
this.personDao = personDao;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* 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.
@ -18,12 +18,12 @@ package org.springframework.test.web.servlet.samples.standalone;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -113,8 +113,6 @@ public class AsyncTests {
.andExpect(request().asyncStarted())
.andReturn();
this.asyncController.onMessage("Joe");
this.mockMvc.perform(asyncDispatch(mvcResult))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
@ -151,8 +149,6 @@ public class AsyncTests {
.andExpect(request().asyncStarted())
.andReturn();
this.asyncController.onMessage("Joe");
this.mockMvc.perform(asyncDispatch(mvcResult))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
@ -183,8 +179,6 @@ public class AsyncTests {
assertThat(writer.toString().contains("Async started = true")).isTrue();
writer = new StringWriter();
this.asyncController.onMessage("Joe");
this.mockMvc.perform(asyncDispatch(mvcResult))
.andDo(print(writer))
.andExpect(status().isOk())
@ -199,10 +193,6 @@ public class AsyncTests {
@RequestMapping(path = "/{id}", produces = "application/json")
private static class AsyncController {
private final Collection<DeferredResult<Person>> deferredResults = new CopyOnWriteArrayList<>();
private final Collection<ListenableFutureTask<Person>> futureTasks = new CopyOnWriteArrayList<>();
@RequestMapping(params = "callable")
public Callable<Person> getCallable() {
return () -> new Person("Joe");
@ -235,9 +225,9 @@ public class AsyncTests {
@RequestMapping(params = "deferredResult")
public DeferredResult<Person> getDeferredResult() {
DeferredResult<Person> deferredResult = new DeferredResult<>();
this.deferredResults.add(deferredResult);
return deferredResult;
DeferredResult<Person> result = new DeferredResult<>();
delay(100, () -> result.setResult(new Person("Joe")));
return result;
}
@RequestMapping(params = "deferredResultWithImmediateValue")
@ -249,26 +239,15 @@ public class AsyncTests {
@RequestMapping(params = "deferredResultWithDelayedError")
public DeferredResult<Person> getDeferredResultWithDelayedError() {
final DeferredResult<Person> deferredResult = new DeferredResult<>();
new Thread() {
@Override
public void run() {
try {
Thread.sleep(100);
deferredResult.setErrorResult(new RuntimeException("Delayed Error"));
}
catch (InterruptedException e) {
/* no-op */
}
}
}.start();
return deferredResult;
DeferredResult<Person> result = new DeferredResult<>();
delay(100, () -> result.setErrorResult(new RuntimeException("Delayed Error")));
return result;
}
@RequestMapping(params = "listenableFuture")
public ListenableFuture<Person> getListenableFuture() {
ListenableFutureTask<Person> futureTask = new ListenableFutureTask<>(() -> new Person("Joe"));
this.futureTasks.add(futureTask);
delay(100, futureTask);
return futureTask;
}
@ -281,19 +260,12 @@ public class AsyncTests {
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String errorHandler(Exception e) {
return e.getMessage();
public String errorHandler(Exception ex) {
return ex.getMessage();
}
void onMessage(String name) {
for (DeferredResult<Person> deferredResult : this.deferredResults) {
deferredResult.setResult(new Person(name));
this.deferredResults.remove(deferredResult);
}
for (ListenableFutureTask<Person> futureTask : this.futureTasks) {
futureTask.run();
this.futureTasks.remove(futureTask);
}
private void delay(long millis, Runnable task) {
Mono.delay(Duration.ofMillis(millis)).doOnTerminate(task).subscribe();
}
}

View File

@ -42,7 +42,7 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
* @author Rossen Stoyanchev
* @author Sam Brannen
*/
class ExceptionHandlerTests {
public class ExceptionHandlerTests {
@Nested
class MvcTests {
@ -62,14 +62,6 @@ class ExceptionHandlerTests {
.andExpect(status().isOk())
.andExpect(forwardedUrl("globalErrorView"));
}
@Test
void globalExceptionHandlerMethodUsingClassArgument() throws Exception {
standaloneSetup(PersonController.class).setControllerAdvice(GlobalExceptionHandler.class).build()
.perform(get("/person/Bonnie"))
.andExpect(status().isOk())
.andExpect(forwardedUrl("globalErrorView"));
}
}
@ -146,7 +138,7 @@ class ExceptionHandlerTests {
void noHandlerFound() throws Exception {
standaloneSetup(RestPersonController.class)
.setControllerAdvice(RestGlobalExceptionHandler.class, RestPersonControllerExceptionHandler.class)
.addDispatcherServletCustomizer(dispatcherServlet -> dispatcherServlet.setThrowExceptionIfNoHandlerFound(true))
.addDispatcherServletCustomizer(servlet -> servlet.setThrowExceptionIfNoHandlerFound(true))
.build()
.perform(get("/bogus").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())

View File

@ -48,9 +48,12 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
* @author Sam Brannen
* @see org.springframework.test.web.servlet.result.PrintingResultHandlerTests
*/
@Disabled("Not intended to be executed with the build. Comment out this line to inspect the output manually.")
@Disabled
public class PrintingResultHandlerSmokeTests {
// Not intended to be executed with the build.
// Comment out class-level @Disabled to see the output.
@Test
public void testPrint() throws Exception {
StringWriter writer = new StringWriter();

View File

@ -41,7 +41,7 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
* @author Rossen Stoyanchev
* @author Sam Brannen
*/
class FlashAttributeAssertionTests {
public class FlashAttributeAssertionTests {
private final MockMvc mockMvc = standaloneSetup(new PersonController())
.alwaysExpect(status().isFound())

View File

@ -49,7 +49,7 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
*
* @author Rossen Stoyanchev
*/
class ModelAssertionTests {
public class ModelAssertionTests {
private MockMvc mockMvc;

View File

@ -36,7 +36,7 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
*
* @author Rossen Stoyanchev
*/
class RequestAttributeAssertionTests {
public class RequestAttributeAssertionTests {
private final MockMvc mockMvc = standaloneSetup(new SimpleController()).build();

View File

@ -43,7 +43,7 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
* @author Rossen Stoyanchev
* @author Sam Brannen
*/
class SessionAttributeAssertionTests {
public class SessionAttributeAssertionTests {
private final MockMvc mockMvc = standaloneSetup(new SimpleController())
.defaultRequest(get("/"))