Browse Source

Align RestTestClient and WebTestClient structure

See gh-34428
pull/35262/head
rstoyanchev 5 months ago
parent
commit
db4696ceae
  1. 24
      spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java
  2. 3
      spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java
  3. 1
      spring-test/src/main/java/org/springframework/test/web/servlet/client/CookieAssertions.java
  4. 197
      spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClient.java
  5. 33
      spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClientBuilder.java
  6. 1
      spring-test/src/main/java/org/springframework/test/web/servlet/client/HeaderAssertions.java
  7. 2
      spring-test/src/main/java/org/springframework/test/web/servlet/client/JsonPathAssertions.java
  8. 511
      spring-test/src/main/java/org/springframework/test/web/servlet/client/RestTestClient.java
  9. 2
      spring-test/src/main/java/org/springframework/test/web/servlet/client/StatusAssertions.java
  10. 1
      spring-test/src/main/java/org/springframework/test/web/servlet/client/XpathAssertions.java

24
spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java

@ -261,18 +261,6 @@ class DefaultWebTestClient implements WebTestClient { @@ -261,18 +261,6 @@ class DefaultWebTestClient implements WebTestClient {
return this;
}
@Override
public RequestBodySpec attribute(String name, Object value) {
this.attributes.put(name, value);
return this;
}
@Override
public RequestBodySpec attributes(Consumer<Map<String, Object>> attributesConsumer) {
attributesConsumer.accept(this.attributes);
return this;
}
@Override
public RequestBodySpec accept(MediaType... acceptableMediaTypes) {
getHeaders().setAccept(Arrays.asList(acceptableMediaTypes));
@ -321,6 +309,18 @@ class DefaultWebTestClient implements WebTestClient { @@ -321,6 +309,18 @@ class DefaultWebTestClient implements WebTestClient {
return this;
}
@Override
public RequestBodySpec attribute(String name, Object value) {
this.attributes.put(name, value);
return this;
}
@Override
public RequestBodySpec attributes(Consumer<Map<String, Object>> attributesConsumer) {
attributesConsumer.accept(this.attributes);
return this;
}
@Override
public RequestBodySpec apiVersion(Object version) {
this.apiVersion = version;

3
spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java

@ -820,6 +820,7 @@ public interface WebTestClient { @@ -820,6 +820,7 @@ public interface WebTestClient {
interface RequestHeadersUriSpec<S extends RequestHeadersSpec<S>> extends UriSpec<S>, RequestHeadersSpec<S> {
}
/**
* Specification for providing the body and the URI of a request.
*/
@ -932,7 +933,6 @@ public interface WebTestClient { @@ -932,7 +933,6 @@ public interface WebTestClient {
@FunctionalInterface
interface ResponseSpecConsumer extends Consumer<ResponseSpec> {
}
}
@ -1014,7 +1014,6 @@ public interface WebTestClient { @@ -1014,7 +1014,6 @@ public interface WebTestClient {
* Spec for expectations on the response body content.
*/
interface BodyContentSpec {
/**
* Assert the response body is empty and return the exchange result.
*/

1
spring-test/src/main/java/org/springframework/test/web/servlet/client/CookieAssertions.java

@ -24,6 +24,7 @@ import org.springframework.util.MultiValueMap; @@ -24,6 +24,7 @@ import org.springframework.util.MultiValueMap;
* Assertions on cookies of the response.
*
* @author Rob Worsnop
* @since 7.0
*/
public class CookieAssertions extends AbstractCookieAssertions<ExchangeResult, RestTestClient.ResponseSpec> {

197
spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClient.java

@ -50,6 +50,8 @@ import org.springframework.web.util.UriBuilder; @@ -50,6 +50,8 @@ import org.springframework.web.util.UriBuilder;
* Default implementation of {@link RestTestClient}.
*
* @author Rob Worsnop
* @author Rossen Stoyanchev
* @since 7.0
*/
class DefaultRestTestClient implements RestTestClient {
@ -59,11 +61,13 @@ class DefaultRestTestClient implements RestTestClient { @@ -59,11 +61,13 @@ class DefaultRestTestClient implements RestTestClient {
private final RestClient.Builder restClientBuilder;
DefaultRestTestClient(RestClient.Builder restClientBuilder) {
this.restClient = restClientBuilder.build();
this.restClientBuilder = restClientBuilder;
}
@Override
public RequestHeadersUriSpec<?> get() {
return methodInternal(HttpMethod.GET);
@ -104,74 +108,75 @@ class DefaultRestTestClient implements RestTestClient { @@ -104,74 +108,75 @@ class DefaultRestTestClient implements RestTestClient {
return methodInternal(method);
}
private RequestBodyUriSpec methodInternal(HttpMethod httpMethod) {
return new DefaultRequestBodyUriSpec(this.restClient.method(httpMethod));
}
@Override
public <B extends Builder<B>> Builder<B> mutate() {
return new DefaultRestTestClientBuilder<>(this.restClientBuilder);
}
private RequestBodyUriSpec methodInternal(HttpMethod httpMethod) {
return new DefaultRequestBodyUriSpec(this.restClient.method(httpMethod));
}
private class DefaultRequestBodyUriSpec implements RequestBodyUriSpec {
private final RestClient.RequestBodyUriSpec requestHeadersUriSpec;
private RestClient.RequestBodySpec requestBodySpec;
private final String requestId;
private final String requestId;
public DefaultRequestBodyUriSpec(RestClient.RequestBodyUriSpec spec) {
DefaultRequestBodyUriSpec(RestClient.RequestBodyUriSpec spec) {
this.requestHeadersUriSpec = spec;
this.requestBodySpec = spec;
this.requestId = String.valueOf(requestIndex.incrementAndGet());
}
@Override
public RequestBodySpec accept(MediaType... acceptableMediaTypes) {
this.requestBodySpec = this.requestHeadersUriSpec.accept(acceptableMediaTypes);
public RequestBodySpec uri(String uriTemplate, Object... uriVariables) {
this.requestBodySpec = this.requestHeadersUriSpec.uri(uriTemplate, uriVariables);
return this;
}
@Override
public RequestBodySpec uri(URI uri) {
this.requestBodySpec = this.requestHeadersUriSpec.uri(uri);
public RequestBodySpec uri(String uri, Map<String, ?> uriVariables) {
this.requestBodySpec = this.requestHeadersUriSpec.uri(uri, uriVariables);
return this;
}
@Override
public RequestBodySpec uri(String uriTemplate, Object... uriVariables) {
this.requestBodySpec = this.requestHeadersUriSpec.uri(uriTemplate, uriVariables);
public RequestBodySpec uri(Function<UriBuilder, URI> uriFunction) {
this.requestBodySpec = this.requestHeadersUriSpec.uri(uriFunction);
return this;
}
@Override
public RequestBodySpec uri(String uri, Map<String, ?> uriVariables) {
this.requestBodySpec = this.requestHeadersUriSpec.uri(uri, uriVariables);
public RequestBodySpec uri(URI uri) {
this.requestBodySpec = this.requestHeadersUriSpec.uri(uri);
return this;
}
@Override
public RequestBodySpec uri(Function<UriBuilder, URI> uriFunction) {
this.requestBodySpec = this.requestHeadersUriSpec.uri(uriFunction);
public RequestBodySpec header(String headerName, String... headerValues) {
this.requestBodySpec = this.requestHeadersUriSpec.header(headerName, headerValues);
return this;
}
@Override
public RequestBodySpec cookie(String name, String value) {
this.requestBodySpec = this.requestHeadersUriSpec.cookie(name, value);
public RequestBodySpec headers(Consumer<HttpHeaders> headersConsumer) {
this.requestBodySpec = this.requestHeadersUriSpec.headers(headersConsumer);
return this;
}
@Override
public RequestBodySpec cookies(Consumer<MultiValueMap<String, String>> cookiesConsumer) {
this.requestBodySpec = this.requestHeadersUriSpec.cookies(cookiesConsumer);
public RequestBodySpec accept(MediaType... acceptableMediaTypes) {
this.requestBodySpec = this.requestHeadersUriSpec.accept(acceptableMediaTypes);
return this;
}
@Override
public RequestBodySpec header(String headerName, String... headerValues) {
this.requestBodySpec = this.requestHeadersUriSpec.header(headerName, headerValues);
public RequestBodySpec acceptCharset(Charset... acceptableCharsets) {
this.requestBodySpec = this.requestHeadersUriSpec.acceptCharset(acceptableCharsets);
return this;
}
@ -182,14 +187,14 @@ class DefaultRestTestClient implements RestTestClient { @@ -182,14 +187,14 @@ class DefaultRestTestClient implements RestTestClient {
}
@Override
public RequestHeadersSpec<?> body(Object body) {
this.requestHeadersUriSpec.body(body);
public RequestBodySpec cookie(String name, String value) {
this.requestBodySpec = this.requestHeadersUriSpec.cookie(name, value);
return this;
}
@Override
public RequestBodySpec acceptCharset(Charset... acceptableCharsets) {
this.requestBodySpec = this.requestHeadersUriSpec.acceptCharset(acceptableCharsets);
public RequestBodySpec cookies(Consumer<MultiValueMap<String, String>> cookiesConsumer) {
this.requestBodySpec = this.requestHeadersUriSpec.cookies(cookiesConsumer);
return this;
}
@ -205,12 +210,6 @@ class DefaultRestTestClient implements RestTestClient { @@ -205,12 +210,6 @@ class DefaultRestTestClient implements RestTestClient {
return this;
}
@Override
public RequestBodySpec headers(Consumer<HttpHeaders> headersConsumer) {
this.requestBodySpec = this.requestHeadersUriSpec.headers(headersConsumer);
return this;
}
@Override
public RequestBodySpec attribute(String name, Object value) {
this.requestBodySpec = this.requestHeadersUriSpec.attribute(name, value);
@ -223,6 +222,12 @@ class DefaultRestTestClient implements RestTestClient { @@ -223,6 +222,12 @@ class DefaultRestTestClient implements RestTestClient {
return this;
}
@Override
public RequestHeadersSpec<?> body(Object body) {
this.requestHeadersUriSpec.body(body);
return this;
}
@Override
public ResponseSpec exchange() {
this.requestBodySpec = this.requestBodySpec.header(RESTTESTCLIENT_REQUEST_ID, this.requestId);
@ -233,11 +238,12 @@ class DefaultRestTestClient implements RestTestClient { @@ -233,11 +238,12 @@ class DefaultRestTestClient implements RestTestClient {
}
}
private static class DefaultResponseSpec implements ResponseSpec {
private final ExchangeResult exchangeResult;
public DefaultResponseSpec(ExchangeResult exchangeResult) {
DefaultResponseSpec(ExchangeResult exchangeResult) {
this.exchangeResult = exchangeResult;
}
@ -247,9 +253,13 @@ class DefaultRestTestClient implements RestTestClient { @@ -247,9 +253,13 @@ class DefaultRestTestClient implements RestTestClient {
}
@Override
public BodyContentSpec expectBody() {
byte[] body = this.exchangeResult.getBody(byte[].class);
return new DefaultBodyContentSpec( new EntityExchangeResult<>(this.exchangeResult, body));
public HeaderAssertions expectHeader() {
return new HeaderAssertions(this.exchangeResult, this);
}
@Override
public CookieAssertions expectCookie() {
return new CookieAssertions(this.exchangeResult, this);
}
@Override
@ -265,13 +275,19 @@ class DefaultRestTestClient implements RestTestClient { @@ -265,13 +275,19 @@ class DefaultRestTestClient implements RestTestClient {
}
@Override
public CookieAssertions expectCookie() {
return new CookieAssertions(this.exchangeResult, this);
public BodyContentSpec expectBody() {
byte[] body = this.exchangeResult.getBody(byte[].class);
return new DefaultBodyContentSpec( new EntityExchangeResult<>(this.exchangeResult, body));
}
@Override
public HeaderAssertions expectHeader() {
return new HeaderAssertions(this.exchangeResult, this);
public <T> EntityExchangeResult<T> returnResult(Class<T> elementClass) {
return new EntityExchangeResult<>(this.exchangeResult, this.exchangeResult.getBody(elementClass));
}
@Override
public <T> EntityExchangeResult<T> returnResult(ParameterizedTypeReference<T> elementTypeRef) {
return new EntityExchangeResult<>(this.exchangeResult, this.exchangeResult.getBody(elementTypeRef));
}
@Override
@ -295,22 +311,63 @@ class DefaultRestTestClient implements RestTestClient { @@ -295,22 +311,63 @@ class DefaultRestTestClient implements RestTestClient {
}
return this;
}
}
private static class DefaultBodySpec<B, S extends BodySpec<B, S>> implements BodySpec<B, S> {
private final EntityExchangeResult<B> result;
DefaultBodySpec(@Nullable EntityExchangeResult<B> result) {
this.result = Objects.requireNonNull(result, "exchangeResult must be non-null");
}
@Override
public <T> EntityExchangeResult<T> returnResult(Class<T> elementClass) {
return new EntityExchangeResult<>(this.exchangeResult, this.exchangeResult.getBody(elementClass));
public <T extends S> T isEqualTo(B expected) {
this.result.assertWithDiagnostics(() ->
AssertionErrors.assertEquals("Response body", expected, this.result.getResponseBody()));
return self();
}
@Override
public <T> EntityExchangeResult<T> returnResult(ParameterizedTypeReference<T> elementTypeRef) {
return new EntityExchangeResult<>(this.exchangeResult, this.exchangeResult.getBody(elementTypeRef));
@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1129
public <T extends S, R> T value(Function<B, R> bodyMapper, Matcher<? super R> matcher) {
this.result.assertWithDiagnostics(() -> {
B body = this.result.getResponseBody();
MatcherAssert.assertThat(bodyMapper.apply(body), matcher);
});
return self();
}
@Override
public <T extends S> T value(Consumer<B> consumer) {
this.result.assertWithDiagnostics(() -> consumer.accept(this.result.getResponseBody()));
return self();
}
@Override
public <T extends S> T consumeWith(Consumer<EntityExchangeResult<B>> consumer) {
this.result.assertWithDiagnostics(() -> consumer.accept(this.result));
return self();
}
@SuppressWarnings("unchecked")
private <T extends S> T self() {
return (T) this;
}
@Override
public EntityExchangeResult<B> returnResult() {
return this.result;
}
}
private static class DefaultBodyContentSpec implements BodyContentSpec {
private final EntityExchangeResult<byte[]> result;
public DefaultBodyContentSpec(EntityExchangeResult<byte[]> result) {
DefaultBodyContentSpec(EntityExchangeResult<byte[]> result) {
this.result = result;
}
@ -374,56 +431,14 @@ class DefaultRestTestClient implements RestTestClient { @@ -374,56 +431,14 @@ class DefaultRestTestClient implements RestTestClient {
}
@Override
public EntityExchangeResult<byte[]> returnResult() {
return this.result;
}
}
private static class DefaultBodySpec<B, S extends BodySpec<B, S>> implements BodySpec<B, S> {
private final EntityExchangeResult<B> result;
public DefaultBodySpec(@Nullable EntityExchangeResult<B> result) {
this.result = Objects.requireNonNull(result, "exchangeResult must be non-null");
public BodyContentSpec consumeWith(Consumer<EntityExchangeResult<byte[]>> consumer) {
this.result.assertWithDiagnostics(() -> consumer.accept(this.result));
return this;
}
@Override
public EntityExchangeResult<B> returnResult() {
public EntityExchangeResult<byte[]> returnResult() {
return this.result;
}
@Override
public <T extends S> T isEqualTo(B expected) {
this.result.assertWithDiagnostics(() ->
AssertionErrors.assertEquals("Response body", expected, this.result.getResponseBody()));
return self();
}
@Override
@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1129
public <T extends S, R> T value(Function<B, R> bodyMapper, Matcher<? super R> matcher) {
this.result.assertWithDiagnostics(() -> {
B body = this.result.getResponseBody();
MatcherAssert.assertThat(bodyMapper.apply(body), matcher);
});
return self();
}
@Override
public <T extends S> T value(Consumer<B> consumer) {
this.result.assertWithDiagnostics(() -> consumer.accept(this.result.getResponseBody()));
return self();
}
@Override
public <T extends S> T consumeWith(Consumer<EntityExchangeResult<B>> consumer) {
this.result.assertWithDiagnostics(() -> consumer.accept(this.result));
return self();
}
@SuppressWarnings("unchecked")
private <T extends S> T self() {
return (T) this;
}
}
}

33
spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClientBuilder.java

@ -25,13 +25,17 @@ import org.springframework.web.util.UriBuilderFactory; @@ -25,13 +25,17 @@ import org.springframework.web.util.UriBuilderFactory;
/**
* Default implementation of {@link RestTestClient.Builder}.
*
* @author Rob Worsnop
* @author Rossen Stoyanchev
* @param <B> the type of the builder
* @since 7.0
*/
class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implements RestTestClient.Builder<B> {
protected final RestClient.Builder restClientBuilder;
DefaultRestTestClientBuilder() {
this.restClientBuilder = RestClient.builder();
}
@ -40,45 +44,46 @@ class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implemen @@ -40,45 +44,46 @@ class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implemen
this.restClientBuilder = restClientBuilder;
}
@Override
public RestTestClient.Builder<B> apply(Consumer<RestTestClient.Builder<B>> builderConsumer) {
builderConsumer.accept(this);
public RestTestClient.Builder<B> baseUrl(String baseUrl) {
this.restClientBuilder.baseUrl(baseUrl);
return this;
}
@Override
public RestTestClient.Builder<B> baseUrl(String baseUrl) {
this.restClientBuilder.baseUrl(baseUrl);
public RestTestClient.Builder<B> uriBuilderFactory(UriBuilderFactory uriFactory) {
this.restClientBuilder.uriBuilderFactory(uriFactory);
return this;
}
@Override
public RestTestClient.Builder<B> defaultCookie(String cookieName, String... cookieValues) {
this.restClientBuilder.defaultCookie(cookieName, cookieValues);
public RestTestClient.Builder<B> defaultHeader(String headerName, String... headerValues) {
this.restClientBuilder.defaultHeader(headerName, headerValues);
return this;
}
@Override
public RestTestClient.Builder<B> defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer) {
this.restClientBuilder.defaultCookies(cookiesConsumer);
public RestTestClient.Builder<B> defaultHeaders(Consumer<HttpHeaders> headersConsumer) {
this.restClientBuilder.defaultHeaders(headersConsumer);
return this;
}
@Override
public RestTestClient.Builder<B> defaultHeader(String headerName, String... headerValues) {
this.restClientBuilder.defaultHeader(headerName, headerValues);
public RestTestClient.Builder<B> defaultCookie(String cookieName, String... cookieValues) {
this.restClientBuilder.defaultCookie(cookieName, cookieValues);
return this;
}
@Override
public RestTestClient.Builder<B> defaultHeaders(Consumer<HttpHeaders> headersConsumer) {
this.restClientBuilder.defaultHeaders(headersConsumer);
public RestTestClient.Builder<B> defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer) {
this.restClientBuilder.defaultCookies(cookiesConsumer);
return this;
}
@Override
public RestTestClient.Builder<B> uriBuilderFactory(UriBuilderFactory uriFactory) {
this.restClientBuilder.uriBuilderFactory(uriFactory);
public RestTestClient.Builder<B> apply(Consumer<RestTestClient.Builder<B>> builderConsumer) {
builderConsumer.accept(this);
return this;
}

1
spring-test/src/main/java/org/springframework/test/web/servlet/client/HeaderAssertions.java

@ -23,6 +23,7 @@ import org.springframework.test.web.support.AbstractHeaderAssertions; @@ -23,6 +23,7 @@ import org.springframework.test.web.support.AbstractHeaderAssertions;
* Assertions on headers of the response.
*
* @author Rob Worsnop
* @since 7.0
* @see RestTestClient.ResponseSpec#expectHeader()
*/
public class HeaderAssertions extends AbstractHeaderAssertions<ExchangeResult, RestTestClient.ResponseSpec> {

2
spring-test/src/main/java/org/springframework/test/web/servlet/client/JsonPathAssertions.java

@ -26,7 +26,7 @@ import org.springframework.test.web.support.AbstractJsonPathAssertions; @@ -26,7 +26,7 @@ import org.springframework.test.web.support.AbstractJsonPathAssertions;
* <a href="https://github.com/jayway/JsonPath">JsonPath</a> assertions.
*
* @author Rob Worsnop
*
* @since 7.0
* @see <a href="https://github.com/jayway/JsonPath">https://github.com/jayway/JsonPath</a>
* @see JsonPathExpectationsHelper
*/

511
spring-test/src/main/java/org/springframework/test/web/servlet/client/RestTestClient.java

@ -63,6 +63,7 @@ public interface RestTestClient { @@ -63,6 +63,7 @@ public interface RestTestClient {
*/
String RESTTESTCLIENT_REQUEST_ID = "RestTestClient-Request-Id";
/**
* Prepare an HTTP GET request.
* @return a spec for specifying the target URL
@ -111,11 +112,13 @@ public interface RestTestClient { @@ -111,11 +112,13 @@ public interface RestTestClient {
*/
RequestBodyUriSpec method(HttpMethod method);
/**
* Return a builder to mutate properties of this test client.
*/
<B extends Builder<B>> Builder<B> mutate();
/**
* Begin creating a {@link RestTestClient} by providing the {@code @Controller}
* instance(s) to handle requests with.
@ -184,239 +187,78 @@ public interface RestTestClient { @@ -184,239 +187,78 @@ public interface RestTestClient {
return new DefaultRestTestClientBuilder<>(RestClient.builder().requestFactory(requestFactory));
}
/**
* Specification for providing request headers and the URI of a request.
*
* @param <S> a self reference to the spec type
*/
interface RequestHeadersUriSpec<S extends RequestHeadersSpec<S>> extends UriSpec<S>, RequestHeadersSpec<S> {
}
/**
* Specification for providing the body and the URI of a request.
*/
interface RequestBodyUriSpec extends RequestBodySpec, RequestHeadersUriSpec<RequestBodySpec> {
}
/**
* Chained API for applying assertions to a response.
*/
interface ResponseSpec {
/**
* Assertions on the response status.
*/
StatusAssertions expectStatus();
/**
* Consume and decode the response body to {@code byte[]} and then apply
* assertions on the raw content (for example, isEmpty, JSONPath, etc.).
*/
BodyContentSpec expectBody();
interface Builder<B extends Builder<B>> {
/**
* Consume and decode the response body to a single object of type
* {@code <B>} and then apply assertions.
* @param bodyType the expected body type
* Configure a base URI as described in
* {@link RestClient#create(String)
* WebClient.create(String)}.
*/
<B> BodySpec<B, ?> expectBody(Class<B> bodyType);
Builder<B> baseUrl(String baseUrl);
/**
* Alternative to {@link #expectBody(Class)} that accepts information
* about a target type with generics.
* Provide a pre-configured {@link UriBuilderFactory} instance as an
* alternative to and effectively overriding {@link #baseUrl(String)}.
*/
<B> BodySpec<B, ?> expectBody(ParameterizedTypeReference<B> bodyType);
Builder<B> uriBuilderFactory(UriBuilderFactory uriBuilderFactory);
/**
* Assertions on the cookies of the response.
* Add the given header to all requests that haven't added it.
* @param headerName the header name
* @param headerValues the header values
*/
CookieAssertions expectCookie();
Builder<B> defaultHeader(String headerName, String... headerValues);
/**
* Assertions on the headers of the response.
* Manipulate the default headers with the given consumer. The
* headers provided to the consumer are "live", so that the consumer can be used to
* {@linkplain HttpHeaders#set(String, String) overwrite} existing header values,
* {@linkplain HttpHeaders#remove(String) remove} values, or use any of the other
* {@link HttpHeaders} methods.
* @param headersConsumer a function that consumes the {@code HttpHeaders}
* @return this builder
*/
HeaderAssertions expectHeader();
Builder<B> defaultHeaders(Consumer<HttpHeaders> headersConsumer);
/**
* Apply multiple assertions to a response with the given
* {@linkplain RestTestClient.ResponseSpec.ResponseSpecConsumer consumers}, with the guarantee that
* all assertions will be applied even if one or more assertions fails
* with an exception.
* <p>If a single {@link Error} or {@link RuntimeException} is thrown,
* it will be rethrown.
* <p>If multiple exceptions are thrown, this method will throw an
* {@link AssertionError} whose error message is a summary of all the
* exceptions. In addition, each exception will be added as a
* {@linkplain Throwable#addSuppressed(Throwable) suppressed exception} to
* the {@code AssertionError}.
* <p>This feature is similar to the {@code SoftAssertions} support in
* AssertJ and the {@code assertAll()} support in JUnit Jupiter.
*
* <h4>Example</h4>
* <pre class="code">
* restTestClient.get().uri("/hello").exchange()
* .expectAll(
* responseSpec -&gt; responseSpec.expectStatus().isOk(),
* responseSpec -&gt; responseSpec.expectBody(String.class).isEqualTo("Hello, World!")
* );
* </pre>
* @param consumers the list of {@code ResponseSpec} consumers
* Add the given cookie to all requests.
* @param cookieName the cookie name
* @param cookieValues the cookie values
*/
ResponseSpec expectAll(ResponseSpecConsumer... consumers);
Builder<B> defaultCookie(String cookieName, String... cookieValues);
/**
* Exit the chained flow in order to consume the response body
* externally.
* Manipulate the default cookies with the given consumer. The
* map provided to the consumer is "live", so that the consumer can be used to
* {@linkplain MultiValueMap#set(Object, Object) overwrite} existing header values,
* {@linkplain MultiValueMap#remove(Object) remove} values, or use any of the other
* {@link MultiValueMap} methods.
* @param cookiesConsumer a function that consumes the cookies map
* @return this builder
*/
<T> EntityExchangeResult<T> returnResult(Class<T> elementClass);
Builder<B> defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
/**
* Alternative to {@link #returnResult(Class)} that accepts information
* about a target type with generics.
* Apply the given {@code Consumer} to this builder instance.
* <p>This can be useful for applying pre-packaged customizations.
* @param builderConsumer the consumer to apply
*/
<T> EntityExchangeResult<T> returnResult(ParameterizedTypeReference<T> elementTypeRef);
Builder<B> apply(Consumer<Builder<B>> builderConsumer);
/**
* {@link Consumer} of a {@link RestTestClient.ResponseSpec}.
* @see RestTestClient.ResponseSpec#expectAll(RestTestClient.ResponseSpec.ResponseSpecConsumer...)
* Build the {@link RestTestClient} instance.
*/
@FunctionalInterface
interface ResponseSpecConsumer extends Consumer<ResponseSpec> {
}
RestTestClient build();
}
/**
* Spec for expectations on the response body content.
*/
interface BodyContentSpec {
/**
* Assert the response body is empty and return the exchange result.
*/
EntityExchangeResult<Void> isEmpty();
/**
* Parse the expected and actual response content as JSON and perform a
* comparison verifying that they contain the same attribute-value pairs
* regardless of formatting with <em>lenient</em> checking (extensible
* and non-strict array ordering).
* <p>Use of this method requires the
* <a href="https://jsonassert.skyscreamer.org/">JSONassert</a> library
* to be on the classpath.
* @param expectedJson the expected JSON content
* @see #json(String, JsonCompareMode)
*/
default BodyContentSpec json(String expectedJson) {
return json(expectedJson, JsonCompareMode.LENIENT);
}
/**
* Parse the expected and actual response content as JSON and perform a
* comparison using the given {@linkplain JsonCompareMode mode}. If the
* comparison failed, throws an {@link AssertionError} with the message
* of the {@link JsonComparison}.
* <p>Use of this method requires the
* <a href="https://jsonassert.skyscreamer.org/">JSONassert</a> library
* to be on the classpath.
* @param expectedJson the expected JSON content
* @param compareMode the compare mode
* @see #json(String)
*/
BodyContentSpec json(String expectedJson, JsonCompareMode compareMode);
/**
* Parse the expected and actual response content as JSON and perform a
* comparison using the given {@link JsonComparator}. If the comparison
* failed, throws an {@link AssertionError} with the message of the
* {@link JsonComparison}.
* @param expectedJson the expected JSON content
* @param comparator the comparator to use
*/
BodyContentSpec json(String expectedJson, JsonComparator comparator);
/**
* Parse expected and actual response content as XML and assert that
* the two are "similar", i.e. they contain the same elements and
* attributes regardless of order.
* <p>Use of this method requires the
* <a href="https://github.com/xmlunit/xmlunit">XMLUnit</a> library on
* the classpath.
* @param expectedXml the expected XML content.
* @see org.springframework.test.util.XmlExpectationsHelper#assertXmlEqual(String, String)
*/
BodyContentSpec xml(String expectedXml);
/**
* Access to response body assertions using an XPath expression to
* inspect a specific subset of the body.
* <p>The XPath expression can be a parameterized string using
* formatting specifiers as defined in {@link String#format}.
* @param expression the XPath expression
* @param args arguments to parameterize the expression
* @see #xpath(String, Map, Object...)
*/
default XpathAssertions xpath(String expression, Object... args) {
return xpath(expression, null, args);
}
/**
* Access to response body assertions with specific namespaces using an
* XPath expression to inspect a specific subset of the body.
* <p>The XPath expression can be a parameterized string using
* formatting specifiers as defined in {@link String#format}.
* @param expression the XPath expression
* @param namespaces the namespaces to use
* @param args arguments to parameterize the expression
*/
XpathAssertions xpath(String expression, @Nullable Map<String, String> namespaces, Object... args);
interface MockServerBuilder<M extends MockMvcBuilder> extends Builder<MockServerBuilder<M>> {
/**
* Access to response body assertions using a
* <a href="https://github.com/jayway/JsonPath">JsonPath</a> expression
* to inspect a specific subset of the body.
* @param expression the JsonPath expression
*/
JsonPathAssertions jsonPath(String expression);
MockServerBuilder<M> configureServer(Consumer<M> consumer);
/**
* Exit the chained API and return an {@code ExchangeResult} with the
* raw response content.
*/
EntityExchangeResult<byte[]> returnResult();
}
/**
* Spec for expectations on the response body decoded to a single Object.
*
* @param <S> a self reference to the spec type
* @param <B> the body type
*/
interface BodySpec<B, S extends BodySpec<B, S>> {
/**
* Transform the extracted the body with a function, for example, extracting a
* property, and assert the mapped value with a {@link Matcher}.
*/
<T extends S, R> T value(Function<B, R> bodyMapper, Matcher<? super R> matcher);
/**
* Assert the extracted body with a {@link Consumer}.
*/
<T extends S> T value(Consumer<B> consumer);
/**
* Assert the exchange result with the given {@link Consumer}.
*/
<T extends S> T consumeWith(Consumer<EntityExchangeResult<B>> consumer);
/**
* Exit the chained API and return an {@code EntityExchangeResult} with the
* decoded response content.
*/
EntityExchangeResult<B> returnResult();
/**
* Assert the extracted body is equal to the given value.
*/
<T extends S> T isEqualTo(B expected);
}
/**
* Specification for providing the URI of a request.
@ -424,6 +266,7 @@ public interface RestTestClient { @@ -424,6 +266,7 @@ public interface RestTestClient {
* @param <S> a self reference to the spec type
*/
interface UriSpec<S extends RequestHeadersSpec<?>> {
/**
* Specify the URI using an absolute, fully constructed {@link java.net.URI}.
* <p>If a {@link UriBuilderFactory} was configured for the client with
@ -457,12 +300,9 @@ public interface RestTestClient { @@ -457,12 +300,9 @@ public interface RestTestClient {
* @return spec to add headers or perform the exchange
*/
S uri(Function<UriBuilder, URI> uriFunction);
}
/**
* Specification for adding request headers and performing an exchange.
*
@ -564,6 +404,7 @@ public interface RestTestClient { @@ -564,6 +404,7 @@ public interface RestTestClient {
ResponseSpec exchange();
}
/**
* Specification for providing body of a request.
*/
@ -587,70 +428,252 @@ public interface RestTestClient { @@ -587,70 +428,252 @@ public interface RestTestClient {
RequestHeadersSpec<?> body(Object body);
}
interface Builder<B extends Builder<B>> {
/**
* Specification for providing request headers and the URI of a request.
*
* @param <S> a self reference to the spec type
*/
interface RequestHeadersUriSpec<S extends RequestHeadersSpec<S>> extends UriSpec<S>, RequestHeadersSpec<S> {
}
/**
* Specification for providing the body and the URI of a request.
*/
interface RequestBodyUriSpec extends RequestBodySpec, RequestHeadersUriSpec<RequestBodySpec> {
}
/**
* Chained API for applying assertions to a response.
*/
interface ResponseSpec {
/**
* Apply the given {@code Consumer} to this builder instance.
* <p>This can be useful for applying pre-packaged customizations.
* @param builderConsumer the consumer to apply
* Apply multiple assertions to a response with the given
* {@linkplain RestTestClient.ResponseSpec.ResponseSpecConsumer consumers}, with the guarantee that
* all assertions will be applied even if one or more assertions fails
* with an exception.
* <p>If a single {@link Error} or {@link RuntimeException} is thrown,
* it will be rethrown.
* <p>If multiple exceptions are thrown, this method will throw an
* {@link AssertionError} whose error message is a summary of all the
* exceptions. In addition, each exception will be added as a
* {@linkplain Throwable#addSuppressed(Throwable) suppressed exception} to
* the {@code AssertionError}.
* <p>This feature is similar to the {@code SoftAssertions} support in
* AssertJ and the {@code assertAll()} support in JUnit Jupiter.
*
* <h4>Example</h4>
* <pre class="code">
* restTestClient.get().uri("/hello").exchange()
* .expectAll(
* responseSpec -&gt; responseSpec.expectStatus().isOk(),
* responseSpec -&gt; responseSpec.expectBody(String.class).isEqualTo("Hello, World!")
* );
* </pre>
* @param consumers the list of {@code ResponseSpec} consumers
*/
Builder<B> apply(Consumer<Builder<B>> builderConsumer);
ResponseSpec expectAll(ResponseSpecConsumer... consumers);
/**
* Add the given cookie to all requests.
* @param cookieName the cookie name
* @param cookieValues the cookie values
* Assertions on the response status.
*/
Builder<B> defaultCookie(String cookieName, String... cookieValues);
StatusAssertions expectStatus();
/**
* Manipulate the default cookies with the given consumer. The
* map provided to the consumer is "live", so that the consumer can be used to
* {@linkplain MultiValueMap#set(Object, Object) overwrite} existing header values,
* {@linkplain MultiValueMap#remove(Object) remove} values, or use any of the other
* {@link MultiValueMap} methods.
* @param cookiesConsumer a function that consumes the cookies map
* @return this builder
* Assertions on the headers of the response.
*/
Builder<B> defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
HeaderAssertions expectHeader();
/**
* Add the given header to all requests that haven't added it.
* @param headerName the header name
* @param headerValues the header values
* Assertions on the cookies of the response.
*/
Builder<B> defaultHeader(String headerName, String... headerValues);
CookieAssertions expectCookie();
/**
* Manipulate the default headers with the given consumer. The
* headers provided to the consumer are "live", so that the consumer can be used to
* {@linkplain HttpHeaders#set(String, String) overwrite} existing header values,
* {@linkplain HttpHeaders#remove(String) remove} values, or use any of the other
* {@link HttpHeaders} methods.
* @param headersConsumer a function that consumes the {@code HttpHeaders}
* @return this builder
* Consume and decode the response body to a single object of type
* {@code <B>} and then apply assertions.
* @param bodyType the expected body type
*/
Builder<B> defaultHeaders(Consumer<HttpHeaders> headersConsumer);
<B> BodySpec<B, ?> expectBody(Class<B> bodyType);
/**
* Provide a pre-configured {@link UriBuilderFactory} instance as an
* alternative to and effectively overriding {@link #baseUrl(String)}.
* Alternative to {@link #expectBody(Class)} that accepts information
* about a target type with generics.
*/
Builder<B> uriBuilderFactory(UriBuilderFactory uriFactory);
<B> BodySpec<B, ?> expectBody(ParameterizedTypeReference<B> bodyType);
/**
* Build the {@link RestTestClient} instance.
* Consume and decode the response body to {@code byte[]} and then apply
* assertions on the raw content (for example, isEmpty, JSONPath, etc.).
*/
RestTestClient build();
BodyContentSpec expectBody();
/**
* Configure a base URI as described in
* {@link RestClient#create(String)
* WebClient.create(String)}.
* Exit the chained flow in order to consume the response body
* externally.
*/
Builder<B> baseUrl(String baseUrl);
<T> EntityExchangeResult<T> returnResult(Class<T> elementClass);
/**
* Alternative to {@link #returnResult(Class)} that accepts information
* about a target type with generics.
*/
<T> EntityExchangeResult<T> returnResult(ParameterizedTypeReference<T> elementTypeRef);
/**
* {@link Consumer} of a {@link RestTestClient.ResponseSpec}.
* @see RestTestClient.ResponseSpec#expectAll(RestTestClient.ResponseSpec.ResponseSpecConsumer...)
*/
@FunctionalInterface
interface ResponseSpecConsumer extends Consumer<ResponseSpec> {
}
}
interface MockServerBuilder<M extends MockMvcBuilder> extends Builder<MockServerBuilder<M>> {
MockServerBuilder<M> configureServer(Consumer<M> consumer);
/**
* Spec for expectations on the response body decoded to a single Object.
*
* @param <S> a self reference to the spec type
* @param <B> the body type
*/
interface BodySpec<B, S extends BodySpec<B, S>> {
/**
* Assert the extracted body is equal to the given value.
*/
<T extends S> T isEqualTo(B expected);
/**
* Transform the extracted the body with a function, for example, extracting a
* property, and assert the mapped value with a {@link Matcher}.
*/
<T extends S, R> T value(Function<B, R> bodyMapper, Matcher<? super R> matcher);
/**
* Assert the extracted body with a {@link Consumer}.
*/
<T extends S> T value(Consumer<B> consumer);
/**
* Assert the exchange result with the given {@link Consumer}.
*/
<T extends S> T consumeWith(Consumer<EntityExchangeResult<B>> consumer);
/**
* Exit the chained API and return an {@code EntityExchangeResult} with the
* decoded response content.
*/
EntityExchangeResult<B> returnResult();
}
/**
* Spec for expectations on the response body content.
*/
interface BodyContentSpec {
/**
* Assert the response body is empty and return the exchange result.
*/
EntityExchangeResult<Void> isEmpty();
/**
* Parse the expected and actual response content as JSON and perform a
* comparison verifying that they contain the same attribute-value pairs
* regardless of formatting with <em>lenient</em> checking (extensible
* and non-strict array ordering).
* <p>Use of this method requires the
* <a href="https://jsonassert.skyscreamer.org/">JSONassert</a> library
* to be on the classpath.
* @param expectedJson the expected JSON content
* @see #json(String, JsonCompareMode)
*/
default BodyContentSpec json(String expectedJson) {
return json(expectedJson, JsonCompareMode.LENIENT);
}
/**
* Parse the expected and actual response content as JSON and perform a
* comparison using the given {@linkplain JsonCompareMode mode}. If the
* comparison failed, throws an {@link AssertionError} with the message
* of the {@link JsonComparison}.
* <p>Use of this method requires the
* <a href="https://jsonassert.skyscreamer.org/">JSONassert</a> library
* to be on the classpath.
* @param expectedJson the expected JSON content
* @param compareMode the compare mode
* @see #json(String)
*/
BodyContentSpec json(String expectedJson, JsonCompareMode compareMode);
/**
* Parse the expected and actual response content as JSON and perform a
* comparison using the given {@link JsonComparator}. If the comparison
* failed, throws an {@link AssertionError} with the message of the
* {@link JsonComparison}.
* @param expectedJson the expected JSON content
* @param comparator the comparator to use
*/
BodyContentSpec json(String expectedJson, JsonComparator comparator);
/**
* Parse expected and actual response content as XML and assert that
* the two are "similar", i.e. they contain the same elements and
* attributes regardless of order.
* <p>Use of this method requires the
* <a href="https://github.com/xmlunit/xmlunit">XMLUnit</a> library on
* the classpath.
* @param expectedXml the expected XML content.
* @see org.springframework.test.util.XmlExpectationsHelper#assertXmlEqual(String, String)
*/
BodyContentSpec xml(String expectedXml);
/**
* Access to response body assertions using a
* <a href="https://github.com/jayway/JsonPath">JsonPath</a> expression
* to inspect a specific subset of the body.
* @param expression the JsonPath expression
*/
JsonPathAssertions jsonPath(String expression);
/**
* Access to response body assertions using an XPath expression to
* inspect a specific subset of the body.
* <p>The XPath expression can be a parameterized string using
* formatting specifiers as defined in {@link String#format}.
* @param expression the XPath expression
* @param args arguments to parameterize the expression
* @see #xpath(String, Map, Object...)
*/
default XpathAssertions xpath(String expression, Object... args) {
return xpath(expression, null, args);
}
/**
* Access to response body assertions with specific namespaces using an
* XPath expression to inspect a specific subset of the body.
* <p>The XPath expression can be a parameterized string using
* formatting specifiers as defined in {@link String#format}.
* @param expression the XPath expression
* @param namespaces the namespaces to use
* @param args arguments to parameterize the expression
*/
XpathAssertions xpath(String expression, @Nullable Map<String, String> namespaces, Object... args);
/**
* Assert the response body content with the given {@link Consumer}.
* @param consumer the consumer for the response body; the input
* {@code byte[]} may be {@code null} if there was no response body.
*/
BodyContentSpec consumeWith(Consumer<EntityExchangeResult<byte[]>> consumer);
/**
* Exit the chained API and return an {@code ExchangeResult} with the
* raw response content.
*/
EntityExchangeResult<byte[]> returnResult();
}
}

2
spring-test/src/main/java/org/springframework/test/web/servlet/client/StatusAssertions.java

@ -24,7 +24,7 @@ import org.springframework.test.web.support.AbstractStatusAssertions; @@ -24,7 +24,7 @@ import org.springframework.test.web.support.AbstractStatusAssertions;
* Assertions on the response status.
*
* @author Rob Worsnop
*
* @since 7.0
* @see ResponseSpec#expectStatus()
*/
public class StatusAssertions extends AbstractStatusAssertions<ExchangeResult, RestTestClient.ResponseSpec> {

1
spring-test/src/main/java/org/springframework/test/web/servlet/client/XpathAssertions.java

@ -29,6 +29,7 @@ import org.springframework.util.Assert; @@ -29,6 +29,7 @@ import org.springframework.util.Assert;
* XPath assertions for the {@link RestTestClient}.
*
* @author Rob Worsnop
* @since 7.0
*/
public class XpathAssertions extends AbstractXpathAssertions<RestTestClient.BodyContentSpec> {

Loading…
Cancel
Save