Merge branch 'webtestclient-experiment'
This commit is contained in:
commit
ccb719eae3
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 + "'";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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" : "");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.test.web.servlet.client;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.test.web.servlet.DispatcherServletCustomizer;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.RequestBuilder;
|
||||
import org.springframework.test.web.servlet.ResultMatcher;
|
||||
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
|
||||
|
||||
/**
|
||||
* Base class for implementations of {@link MockMvcTestClient.MockMvcServerSpec}
|
||||
* that simply delegates to a {@link ConfigurableMockMvcBuilder} supplied by
|
||||
* the concrete sub-classes.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.3
|
||||
* @param <B> the type of the concrete sub-class spec
|
||||
*/
|
||||
abstract class AbstractMockMvcServerSpec<B extends MockMvcTestClient.MockMvcServerSpec<B>>
|
||||
implements MockMvcTestClient.MockMvcServerSpec<B> {
|
||||
|
||||
@Override
|
||||
public <T extends B> T filters(Filter... filters) {
|
||||
getMockMvcBuilder().addFilters(filters);
|
||||
return self();
|
||||
}
|
||||
|
||||
public final <T extends B> T filter(Filter filter, String... urlPatterns) {
|
||||
getMockMvcBuilder().addFilter(filter, urlPatterns);
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends B> T defaultRequest(RequestBuilder requestBuilder) {
|
||||
getMockMvcBuilder().defaultRequest(requestBuilder);
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends B> T alwaysExpect(ResultMatcher resultMatcher) {
|
||||
getMockMvcBuilder().alwaysExpect(resultMatcher);
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends B> T dispatchOptions(boolean dispatchOptions) {
|
||||
getMockMvcBuilder().dispatchOptions(dispatchOptions);
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends B> T dispatcherServletCustomizer(DispatcherServletCustomizer customizer) {
|
||||
getMockMvcBuilder().addDispatcherServletCustomizer(customizer);
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends B> T apply(MockMvcConfigurer configurer) {
|
||||
getMockMvcBuilder().apply(configurer);
|
||||
return self();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends B> T self() {
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the concrete {@link ConfigurableMockMvcBuilder} to delegate
|
||||
* configuration methods and to use to create the {@link MockMvc}.
|
||||
*/
|
||||
protected abstract ConfigurableMockMvcBuilder<?> getMockMvcBuilder();
|
||||
|
||||
@Override
|
||||
public WebTestClient.Builder configureClient() {
|
||||
MockMvc mockMvc = getMockMvcBuilder().build();
|
||||
ClientHttpConnector connector = new MockMvcHttpConnector(mockMvc);
|
||||
return WebTestClient.bindToServer(connector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebTestClient build() {
|
||||
return configureClient().build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.test.web.servlet.client;
|
||||
|
||||
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
|
||||
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
/**
|
||||
* Simple wrapper around a {@link DefaultMockMvcBuilder}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.3
|
||||
*/
|
||||
class ApplicationContextMockMvcSpec extends AbstractMockMvcServerSpec<ApplicationContextMockMvcSpec> {
|
||||
|
||||
private final DefaultMockMvcBuilder mockMvcBuilder;
|
||||
|
||||
|
||||
public ApplicationContextMockMvcSpec(WebApplicationContext context) {
|
||||
this.mockMvcBuilder = MockMvcBuilders.webAppContextSetup(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConfigurableMockMvcBuilder<?> getMockMvcBuilder() {
|
||||
return this.mockMvcBuilder;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,301 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.test.web.servlet.client;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.DefaultDataBuffer;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.http.HttpCookie;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ReactiveHttpInputMessage;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.ClientHttpRequest;
|
||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||
import org.springframework.http.codec.multipart.DefaultPartHttpMessageReader;
|
||||
import org.springframework.http.codec.multipart.FilePart;
|
||||
import org.springframework.http.codec.multipart.Part;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.mock.http.client.reactive.MockClientHttpRequest;
|
||||
import org.springframework.mock.http.client.reactive.MockClientHttpResponse;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockPart;
|
||||
import org.springframework.test.web.reactive.server.MockServerClientHttpResponse;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.RequestBuilder;
|
||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||
import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.FlashMap;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
|
||||
|
||||
/**
|
||||
* Connector that handles requests by invoking a {@link MockMvc} rather than
|
||||
* making actual requests over HTTP.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.3
|
||||
*/
|
||||
public class MockMvcHttpConnector implements ClientHttpConnector {
|
||||
|
||||
private static final DefaultPartHttpMessageReader MULTIPART_READER = new DefaultPartHttpMessageReader();
|
||||
|
||||
private static final Duration TIMEOUT = Duration.ofSeconds(5);
|
||||
|
||||
|
||||
private final MockMvc mockMvc;
|
||||
|
||||
|
||||
public MockMvcHttpConnector(MockMvc mockMvc) {
|
||||
this.mockMvc = mockMvc;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<ClientHttpResponse> connect(
|
||||
HttpMethod method, URI uri, Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {
|
||||
|
||||
RequestBuilder requestBuilder = adaptRequest(method, uri, requestCallback);
|
||||
try {
|
||||
MvcResult mvcResult = this.mockMvc.perform(requestBuilder).andReturn();
|
||||
if (mvcResult.getRequest().isAsyncStarted()) {
|
||||
mvcResult.getAsyncResult();
|
||||
mvcResult = this.mockMvc.perform(asyncDispatch(mvcResult)).andReturn();
|
||||
}
|
||||
return Mono.just(adaptResponse(mvcResult));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private RequestBuilder adaptRequest(
|
||||
HttpMethod httpMethod, URI uri, Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {
|
||||
|
||||
MockClientHttpRequest httpRequest = new MockClientHttpRequest(httpMethod, uri);
|
||||
|
||||
AtomicReference<byte[]> contentRef = new AtomicReference<>();
|
||||
httpRequest.setWriteHandler(dataBuffers ->
|
||||
DataBufferUtils.join(dataBuffers)
|
||||
.doOnNext(buffer -> {
|
||||
byte[] bytes = new byte[buffer.readableByteCount()];
|
||||
buffer.read(bytes);
|
||||
DataBufferUtils.release(buffer);
|
||||
contentRef.set(bytes);
|
||||
})
|
||||
.then());
|
||||
|
||||
// Initialize the client request
|
||||
requestCallback.apply(httpRequest).block(TIMEOUT);
|
||||
|
||||
MockHttpServletRequestBuilder requestBuilder =
|
||||
initRequestBuilder(httpMethod, uri, httpRequest, contentRef.get());
|
||||
|
||||
requestBuilder.headers(httpRequest.getHeaders());
|
||||
for (List<HttpCookie> cookies : httpRequest.getCookies().values()) {
|
||||
for (HttpCookie cookie : cookies) {
|
||||
requestBuilder.cookie(new Cookie(cookie.getName(), cookie.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
return requestBuilder;
|
||||
}
|
||||
|
||||
private MockHttpServletRequestBuilder initRequestBuilder(
|
||||
HttpMethod httpMethod, URI uri, MockClientHttpRequest httpRequest, @Nullable byte[] bytes) {
|
||||
|
||||
String contentType = httpRequest.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
|
||||
if (!StringUtils.startsWithIgnoreCase(contentType, "multipart/")) {
|
||||
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.request(httpMethod, uri);
|
||||
if (!ObjectUtils.isEmpty(bytes)) {
|
||||
requestBuilder.content(bytes);
|
||||
}
|
||||
return requestBuilder;
|
||||
}
|
||||
|
||||
// Parse the multipart request in order to adapt to Servlet Part's
|
||||
MockMultipartHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.multipart(uri);
|
||||
|
||||
Assert.notNull(bytes, "No multipart content");
|
||||
ReactiveHttpInputMessage inputMessage = MockServerHttpRequest.post(uri.toString())
|
||||
.headers(httpRequest.getHeaders())
|
||||
.body(Mono.just(DefaultDataBufferFactory.sharedInstance.wrap(bytes)));
|
||||
|
||||
MULTIPART_READER.read(ResolvableType.forClass(Part.class), inputMessage, Collections.emptyMap())
|
||||
.flatMap(part ->
|
||||
DataBufferUtils.join(part.content())
|
||||
.doOnNext(buffer -> {
|
||||
byte[] partBytes = new byte[buffer.readableByteCount()];
|
||||
buffer.read(partBytes);
|
||||
DataBufferUtils.release(buffer);
|
||||
|
||||
// Adapt to javax.servlet.http.Part...
|
||||
MockPart mockPart = (part instanceof FilePart ?
|
||||
new MockPart(part.name(), ((FilePart) part).filename(), partBytes) :
|
||||
new MockPart(part.name(), partBytes));
|
||||
mockPart.getHeaders().putAll(part.headers());
|
||||
requestBuilder.part(mockPart);
|
||||
}))
|
||||
.blockLast(TIMEOUT);
|
||||
|
||||
return requestBuilder;
|
||||
}
|
||||
|
||||
private MockClientHttpResponse adaptResponse(MvcResult mvcResult) {
|
||||
MockClientHttpResponse clientResponse = new MockMvcServerClientHttpResponse(mvcResult);
|
||||
MockHttpServletResponse servletResponse = mvcResult.getResponse();
|
||||
for (String header : servletResponse.getHeaderNames()) {
|
||||
for (String value : servletResponse.getHeaders(header)) {
|
||||
clientResponse.getHeaders().add(header, value);
|
||||
}
|
||||
}
|
||||
if (servletResponse.getForwardedUrl() != null) {
|
||||
clientResponse.getHeaders().add("Forwarded-Url", servletResponse.getForwardedUrl());
|
||||
}
|
||||
for (Cookie cookie : servletResponse.getCookies()) {
|
||||
ResponseCookie httpCookie =
|
||||
ResponseCookie.fromClientResponse(cookie.getName(), cookie.getValue())
|
||||
.maxAge(Duration.ofSeconds(cookie.getMaxAge()))
|
||||
.domain(cookie.getDomain())
|
||||
.path(cookie.getPath())
|
||||
.secure(cookie.getSecure())
|
||||
.httpOnly(cookie.isHttpOnly())
|
||||
.build();
|
||||
clientResponse.getCookies().add(httpCookie.getName(), httpCookie);
|
||||
}
|
||||
byte[] bytes = servletResponse.getContentAsByteArray();
|
||||
DefaultDataBuffer dataBuffer = DefaultDataBufferFactory.sharedInstance.wrap(bytes);
|
||||
clientResponse.setBody(Mono.just(dataBuffer));
|
||||
return clientResponse;
|
||||
}
|
||||
|
||||
|
||||
private static class MockMvcServerClientHttpResponse
|
||||
extends MockClientHttpResponse implements MockServerClientHttpResponse {
|
||||
|
||||
private final MvcResult mvcResult;
|
||||
|
||||
|
||||
public MockMvcServerClientHttpResponse(MvcResult result) {
|
||||
super(result.getResponse().getStatus());
|
||||
this.mvcResult = new PrintingMvcResult(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getServerResult() {
|
||||
return this.mvcResult;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class PrintingMvcResult implements MvcResult {
|
||||
|
||||
private final MvcResult mvcResult;
|
||||
|
||||
public PrintingMvcResult(MvcResult mvcResult) {
|
||||
this.mvcResult = mvcResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequest getRequest() {
|
||||
return this.mvcResult.getRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletResponse getResponse() {
|
||||
return this.mvcResult.getResponse();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object getHandler() {
|
||||
return this.mvcResult.getHandler();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public HandlerInterceptor[] getInterceptors() {
|
||||
return this.mvcResult.getInterceptors();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ModelAndView getModelAndView() {
|
||||
return this.mvcResult.getModelAndView();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Exception getResolvedException() {
|
||||
return this.mvcResult.getResolvedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlashMap getFlashMap() {
|
||||
return this.mvcResult.getFlashMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAsyncResult() {
|
||||
return this.mvcResult.getAsyncResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAsyncResult(long timeToWait) {
|
||||
return this.mvcResult.getAsyncResult(timeToWait);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringWriter writer = new StringWriter();
|
||||
try {
|
||||
MockMvcResultHandlers.print(writer).handle(this);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
writer.append("Unable to format ")
|
||||
.append(String.valueOf(this))
|
||||
.append(": ")
|
||||
.append(ex.getMessage());
|
||||
}
|
||||
return writer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,380 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.test.web.servlet.client;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
|
||||
import org.springframework.format.support.FormattingConversionService;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.web.reactive.server.ExchangeResult;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.test.web.servlet.DispatcherServletCustomizer;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.RequestBuilder;
|
||||
import org.springframework.test.web.servlet.ResultActions;
|
||||
import org.springframework.test.web.servlet.ResultHandler;
|
||||
import org.springframework.test.web.servlet.ResultMatcher;
|
||||
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
|
||||
import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.web.servlet.FlashMapManager;
|
||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
import org.springframework.web.servlet.View;
|
||||
import org.springframework.web.servlet.ViewResolver;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
/**
|
||||
* The main class for testing Spring MVC applications via {@link WebTestClient}
|
||||
* with {@link MockMvc} for server request handling.
|
||||
*
|
||||
* <p>Provides static factory methods and specs to initialize {@code MockMvc}
|
||||
* to which the {@code WebTestClient} connects to. For example:
|
||||
* <pre class="code">
|
||||
* WebTestClient client = MockMvcTestClient.bindToController(myController)
|
||||
* .controllerAdvice(myControllerAdvice)
|
||||
* .validator(myValidator)
|
||||
* .build()
|
||||
* </pre>
|
||||
*
|
||||
* <p>The client itself can also be configured. For example:
|
||||
* <pre class="code">
|
||||
* WebTestClient client = MockMvcTestClient.bindToController(myController)
|
||||
* .validator(myValidator)
|
||||
* .configureClient()
|
||||
* .baseUrl("/path")
|
||||
* .build();
|
||||
* </pre>
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.3
|
||||
*/
|
||||
public interface MockMvcTestClient {
|
||||
|
||||
/**
|
||||
* Begin creating a {@link WebTestClient} by providing the {@code @Controller}
|
||||
* instance(s) to handle requests with.
|
||||
* <p>Internally this is delegated to and equivalent to using
|
||||
* {@link org.springframework.test.web.servlet.setup.MockMvcBuilders#standaloneSetup(Object...)}.
|
||||
* to initialize {@link MockMvc}.
|
||||
*/
|
||||
static ControllerSpec bindToController(Object... controllers) {
|
||||
return new StandaloneMockMvcSpec(controllers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin creating a {@link WebTestClient} by providing a
|
||||
* {@link WebApplicationContext} with Spring MVC infrastructure and
|
||||
* controllers.
|
||||
* <p>Internally this is delegated to and equivalent to using
|
||||
* {@link org.springframework.test.web.servlet.setup.MockMvcBuilders#webAppContextSetup(WebApplicationContext)}
|
||||
* to initialize {@code MockMvc}.
|
||||
*/
|
||||
static MockMvcServerSpec<?> bindToApplicationContext(WebApplicationContext context) {
|
||||
return new ApplicationContextMockMvcSpec(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin creating a {@link WebTestClient} by providing an already
|
||||
* initialized {@link MockMvc} instance to use as the server.
|
||||
*/
|
||||
static WebTestClient.Builder bindTo(MockMvc mockMvc) {
|
||||
ClientHttpConnector connector = new MockMvcHttpConnector(mockMvc);
|
||||
return WebTestClient.bindToServer(connector);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be used to apply further assertions on a given
|
||||
* {@link ExchangeResult} based the state of the server response.
|
||||
*
|
||||
* <p>Normally {@link WebTestClient} is used to assert the client response
|
||||
* including HTTP status, headers, and body. That is all that is available
|
||||
* when making a live request over HTTP. However when the server is
|
||||
* {@link MockMvc}, many more assertions are possible against the server
|
||||
* response, e.g. model attributes, flash attributes, etc.
|
||||
*
|
||||
* <p>Example:
|
||||
* <pre class="code">
|
||||
* EntityExchangeResult<Void> result =
|
||||
* webTestClient.post().uri("/people/123")
|
||||
* .exchange()
|
||||
* .expectStatus().isFound()
|
||||
* .expectHeader().location("/persons/Joe")
|
||||
* .expectBody().isEmpty();
|
||||
*
|
||||
* MockMvcTestClient.resultActionsFor(result)
|
||||
* .andExpect(model().size(1))
|
||||
* .andExpect(model().attributeExists("name"))
|
||||
* .andExpect(flash().attributeCount(1))
|
||||
* .andExpect(flash().attribute("message", "success!"));
|
||||
* </pre>
|
||||
*
|
||||
* <p>Note: this method works only if the {@link WebTestClient} used to
|
||||
* perform the request was initialized through one of bind method in this
|
||||
* class, and therefore requests are handled by {@link MockMvc}.
|
||||
*/
|
||||
static ResultActions resultActionsFor(ExchangeResult exchangeResult) {
|
||||
Object serverResult = exchangeResult.getMockServerResult();
|
||||
Assert.notNull(serverResult, "No MvcResult");
|
||||
Assert.isInstanceOf(MvcResult.class, serverResult);
|
||||
return new ResultActions() {
|
||||
@Override
|
||||
public ResultActions andExpect(ResultMatcher matcher) throws Exception {
|
||||
matcher.match((MvcResult) serverResult);
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public ResultActions andDo(ResultHandler handler) throws Exception {
|
||||
handler.handle((MvcResult) serverResult);
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public MvcResult andReturn() {
|
||||
return (MvcResult) serverResult;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Base specification for configuring {@link MockMvc}, and a simple facade
|
||||
* around {@link ConfigurableMockMvcBuilder}.
|
||||
*
|
||||
* @param <B> a self reference to the builder type
|
||||
*/
|
||||
interface MockMvcServerSpec<B extends MockMvcServerSpec<B>> {
|
||||
|
||||
/**
|
||||
* Add a global filter.
|
||||
* <p>This is delegated to
|
||||
* {@link ConfigurableMockMvcBuilder#addFilters(Filter...)}.
|
||||
*/
|
||||
<T extends B> T filters(Filter... filters);
|
||||
|
||||
/**
|
||||
* Add a filter for specific URL patterns.
|
||||
* <p>This is delegated to
|
||||
* {@link ConfigurableMockMvcBuilder#addFilter(Filter, String...)}.
|
||||
*/
|
||||
<T extends B> T filter(Filter filter, String... urlPatterns);
|
||||
|
||||
/**
|
||||
* Define default request properties that should be merged into all
|
||||
* performed requests such that input from the client request override
|
||||
* the default properties defined here.
|
||||
* <p>This is delegated to
|
||||
* {@link ConfigurableMockMvcBuilder#defaultRequest(RequestBuilder)}.
|
||||
*/
|
||||
<T extends B> T defaultRequest(RequestBuilder requestBuilder);
|
||||
|
||||
/**
|
||||
* Define a global expectation that should <em>always</em> be applied to
|
||||
* every response.
|
||||
* <p>This is delegated to
|
||||
* {@link ConfigurableMockMvcBuilder#alwaysExpect(ResultMatcher)}.
|
||||
*/
|
||||
<T extends B> T alwaysExpect(ResultMatcher resultMatcher);
|
||||
|
||||
/**
|
||||
* Whether to handle HTTP OPTIONS requests.
|
||||
* <p>This is delegated to
|
||||
* {@link ConfigurableMockMvcBuilder#dispatchOptions(boolean)}.
|
||||
*/
|
||||
<T extends B> T dispatchOptions(boolean dispatchOptions);
|
||||
|
||||
/**
|
||||
* Allow customization of {@code DispatcherServlet}.
|
||||
* <p>This is delegated to
|
||||
* {@link ConfigurableMockMvcBuilder#addDispatcherServletCustomizer(DispatcherServletCustomizer)}.
|
||||
*/
|
||||
<T extends B> T dispatcherServletCustomizer(DispatcherServletCustomizer customizer);
|
||||
|
||||
/**
|
||||
* Add a {@code MockMvcConfigurer} that automates MockMvc setup.
|
||||
* <p>This is delegated to
|
||||
* {@link ConfigurableMockMvcBuilder#apply(MockMvcConfigurer)}.
|
||||
*/
|
||||
<T extends B> T apply(MockMvcConfigurer configurer);
|
||||
|
||||
/**
|
||||
* Proceed to configure and build the test client.
|
||||
*/
|
||||
WebTestClient.Builder configureClient();
|
||||
|
||||
/**
|
||||
* Shortcut to build the test client.
|
||||
*/
|
||||
WebTestClient build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specification for configuring {@link MockMvc} to test one or more
|
||||
* controllers directly, and a simple facade around
|
||||
* {@link StandaloneMockMvcBuilder}.
|
||||
*/
|
||||
interface ControllerSpec extends MockMvcServerSpec<ControllerSpec> {
|
||||
|
||||
/**
|
||||
* Register {@link org.springframework.web.bind.annotation.ControllerAdvice}
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#setControllerAdvice(Object...)}.
|
||||
*/
|
||||
ControllerSpec controllerAdvice(Object... controllerAdvice);
|
||||
|
||||
/**
|
||||
* Set the message converters to use.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#setMessageConverters(HttpMessageConverter[])}.
|
||||
*/
|
||||
ControllerSpec messageConverters(HttpMessageConverter<?>... messageConverters);
|
||||
|
||||
/**
|
||||
* Provide a custom {@link Validator}.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#setValidator(Validator)}.
|
||||
*/
|
||||
ControllerSpec validator(Validator validator);
|
||||
|
||||
/**
|
||||
* Provide a conversion service.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#setConversionService(FormattingConversionService)}.
|
||||
*/
|
||||
ControllerSpec conversionService(FormattingConversionService conversionService);
|
||||
|
||||
/**
|
||||
* Add global interceptors.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#addInterceptors(HandlerInterceptor...)}.
|
||||
*/
|
||||
ControllerSpec interceptors(HandlerInterceptor... interceptors);
|
||||
|
||||
/**
|
||||
* Add interceptors for specific patterns.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#addMappedInterceptors(String[], HandlerInterceptor...)}.
|
||||
*/
|
||||
ControllerSpec mappedInterceptors(
|
||||
@Nullable String[] pathPatterns, HandlerInterceptor... interceptors);
|
||||
|
||||
/**
|
||||
* Set a ContentNegotiationManager.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#setContentNegotiationManager(ContentNegotiationManager)}.
|
||||
*/
|
||||
ControllerSpec contentNegotiationManager(ContentNegotiationManager manager);
|
||||
|
||||
/**
|
||||
* Specify the timeout value for async execution.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#setAsyncRequestTimeout(long)}.
|
||||
*/
|
||||
ControllerSpec asyncRequestTimeout(long timeout);
|
||||
|
||||
/**
|
||||
* Provide custom argument resolvers.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#setCustomArgumentResolvers(HandlerMethodArgumentResolver...)}.
|
||||
*/
|
||||
ControllerSpec customArgumentResolvers(HandlerMethodArgumentResolver... argumentResolvers);
|
||||
|
||||
/**
|
||||
* Provide custom return value handlers.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#setCustomReturnValueHandlers(HandlerMethodReturnValueHandler...)}.
|
||||
*/
|
||||
ControllerSpec customReturnValueHandlers(HandlerMethodReturnValueHandler... handlers);
|
||||
|
||||
/**
|
||||
* Set the HandlerExceptionResolver types to use.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#setHandlerExceptionResolvers(HandlerExceptionResolver...)}.
|
||||
*/
|
||||
ControllerSpec handlerExceptionResolvers(HandlerExceptionResolver... exceptionResolvers);
|
||||
|
||||
/**
|
||||
* Set up view resolution.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#setViewResolvers(ViewResolver...)}.
|
||||
*/
|
||||
ControllerSpec viewResolvers(ViewResolver... resolvers);
|
||||
|
||||
/**
|
||||
* Set up a single {@link ViewResolver} with a fixed view.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#setSingleView(View)}.
|
||||
*/
|
||||
ControllerSpec singleView(View view);
|
||||
|
||||
/**
|
||||
* Provide the LocaleResolver to use.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#setLocaleResolver(LocaleResolver)}.
|
||||
*/
|
||||
ControllerSpec localeResolver(LocaleResolver localeResolver);
|
||||
|
||||
/**
|
||||
* Provide a custom FlashMapManager.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#setFlashMapManager(FlashMapManager)}.
|
||||
*/
|
||||
ControllerSpec flashMapManager(FlashMapManager flashMapManager);
|
||||
|
||||
/**
|
||||
* Enable URL path matching with parsed
|
||||
* {@link org.springframework.web.util.pattern.PathPattern PathPatterns}.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#setPatternParser(PathPatternParser)}.
|
||||
*/
|
||||
ControllerSpec patternParser(PathPatternParser parser);
|
||||
|
||||
/**
|
||||
* Whether to match trailing slashes.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#setUseTrailingSlashPatternMatch(boolean)}.
|
||||
*/
|
||||
ControllerSpec useTrailingSlashPatternMatch(boolean useTrailingSlashPatternMatch);
|
||||
|
||||
/**
|
||||
* Configure placeholder values to use.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#addPlaceholderValue(String, String)}.
|
||||
*/
|
||||
ControllerSpec placeholderValue(String name, String value);
|
||||
|
||||
/**
|
||||
* Configure factory for a custom {@link RequestMappingHandlerMapping}.
|
||||
* <p>This is delegated to
|
||||
* {@link StandaloneMockMvcBuilder#setCustomHandlerMapping(Supplier)}.
|
||||
*/
|
||||
ControllerSpec customHandlerMapping(Supplier<RequestMappingHandlerMapping> factory);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.test.web.servlet.client;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.format.support.FormattingConversionService;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.web.servlet.FlashMapManager;
|
||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
import org.springframework.web.servlet.View;
|
||||
import org.springframework.web.servlet.ViewResolver;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
/**
|
||||
* Simple wrapper around a {@link StandaloneMockMvcBuilder} that implements
|
||||
* {@link MockMvcTestClient.ControllerSpec}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.3
|
||||
*/
|
||||
class StandaloneMockMvcSpec extends AbstractMockMvcServerSpec<MockMvcTestClient.ControllerSpec>
|
||||
implements MockMvcTestClient.ControllerSpec {
|
||||
|
||||
private final StandaloneMockMvcBuilder mockMvcBuilder;
|
||||
|
||||
|
||||
StandaloneMockMvcSpec(Object... controllers) {
|
||||
this.mockMvcBuilder = MockMvcBuilders.standaloneSetup(controllers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec controllerAdvice(Object... controllerAdvice) {
|
||||
this.mockMvcBuilder.setControllerAdvice(controllerAdvice);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec messageConverters(HttpMessageConverter<?>... messageConverters) {
|
||||
this.mockMvcBuilder.setMessageConverters(messageConverters);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec validator(Validator validator) {
|
||||
this.mockMvcBuilder.setValidator(validator);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec conversionService(FormattingConversionService conversionService) {
|
||||
this.mockMvcBuilder.setConversionService(conversionService);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec interceptors(HandlerInterceptor... interceptors) {
|
||||
mappedInterceptors(null, interceptors);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec mappedInterceptors(
|
||||
@Nullable String[] pathPatterns, HandlerInterceptor... interceptors) {
|
||||
|
||||
this.mockMvcBuilder.addMappedInterceptors(pathPatterns, interceptors);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec contentNegotiationManager(ContentNegotiationManager manager) {
|
||||
this.mockMvcBuilder.setContentNegotiationManager(manager);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec asyncRequestTimeout(long timeout) {
|
||||
this.mockMvcBuilder.setAsyncRequestTimeout(timeout);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec customArgumentResolvers(HandlerMethodArgumentResolver... argumentResolvers) {
|
||||
this.mockMvcBuilder.setCustomArgumentResolvers(argumentResolvers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec customReturnValueHandlers(HandlerMethodReturnValueHandler... handlers) {
|
||||
this.mockMvcBuilder.setCustomReturnValueHandlers(handlers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec handlerExceptionResolvers(HandlerExceptionResolver... exceptionResolvers) {
|
||||
this.mockMvcBuilder.setHandlerExceptionResolvers(exceptionResolvers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec viewResolvers(ViewResolver... resolvers) {
|
||||
this.mockMvcBuilder.setViewResolvers(resolvers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec singleView(View view) {
|
||||
this.mockMvcBuilder.setSingleView(view);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec localeResolver(LocaleResolver localeResolver) {
|
||||
this.mockMvcBuilder.setLocaleResolver(localeResolver);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec flashMapManager(FlashMapManager flashMapManager) {
|
||||
this.mockMvcBuilder.setFlashMapManager(flashMapManager);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec patternParser(PathPatternParser parser) {
|
||||
this.mockMvcBuilder.setPatternParser(parser);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec useTrailingSlashPatternMatch(boolean useTrailingSlashPatternMatch) {
|
||||
this.mockMvcBuilder.setUseTrailingSlashPatternMatch(useTrailingSlashPatternMatch);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec placeholderValue(String name, String value) {
|
||||
this.mockMvcBuilder.addPlaceholderValue(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandaloneMockMvcSpec customHandlerMapping(Supplier<RequestMappingHandlerMapping> factory) {
|
||||
this.mockMvcBuilder.setCustomHandlerMapping(factory);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurableMockMvcBuilder<?> getMockMvcBuilder() {
|
||||
return this.mockMvcBuilder;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Support for testing Spring MVC applications via
|
||||
* {@link org.springframework.test.web.reactive.server.WebTestClient}
|
||||
* with {@link org.springframework.test.web.servlet.MockMvc} for server request
|
||||
* handling.
|
||||
*/
|
||||
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.test.web.servlet.client;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
* <filter-mapping>
|
||||
* <filter-name>springSecurityFilterChain</filter-name>
|
||||
|
|
@ -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">
|
||||
* <filter-mapping>
|
||||
* <filter-name>myResourceFilter</filter-name>
|
||||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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(){
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ public class PersonController {
|
|||
private final PersonDao personDao;
|
||||
|
||||
|
||||
PersonController(PersonDao personDao) {
|
||||
public PersonController(PersonDao personDao) {
|
||||
this.personDao = personDao;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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("/"))
|
||||
|
|
|
|||
Loading…
Reference in New Issue