Browse Source

Further alignment of RestTestClient and WebTestClient

See gh-34428
pull/35262/head
rstoyanchev 6 months ago
parent
commit
34f259778e
  1. 4
      spring-test/src/main/java/org/springframework/test/web/reactive/server/CookieAssertions.java
  2. 16
      spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java
  3. 2
      spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java
  4. 5
      spring-test/src/main/java/org/springframework/test/web/reactive/server/JsonPathAssertions.java
  5. 2
      spring-test/src/main/java/org/springframework/test/web/reactive/server/StatusAssertions.java
  6. 50
      spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java
  7. 4
      spring-test/src/main/java/org/springframework/test/web/servlet/client/CookieAssertions.java
  8. 101
      spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClient.java
  9. 45
      spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClientBuilder.java
  10. 78
      spring-test/src/main/java/org/springframework/test/web/servlet/client/ExchangeResult.java
  11. 2
      spring-test/src/main/java/org/springframework/test/web/servlet/client/HeaderAssertions.java
  12. 8
      spring-test/src/main/java/org/springframework/test/web/servlet/client/JsonPathAssertions.java
  13. 124
      spring-test/src/main/java/org/springframework/test/web/servlet/client/RestTestClient.java
  14. 4
      spring-test/src/main/java/org/springframework/test/web/servlet/client/StatusAssertions.java
  15. 8
      spring-test/src/main/java/org/springframework/test/web/servlet/client/XpathAssertions.java
  16. 2
      spring-test/src/test/java/org/springframework/test/web/servlet/client/samples/RestTestClientTests.java

4
spring-test/src/main/java/org/springframework/test/web/reactive/server/CookieAssertions.java

@ -29,10 +29,12 @@ import org.springframework.util.MultiValueMap; @@ -29,10 +29,12 @@ import org.springframework.util.MultiValueMap;
*/
public class CookieAssertions extends AbstractCookieAssertions<ExchangeResult, WebTestClient.ResponseSpec> {
public CookieAssertions(ExchangeResult exchangeResult, WebTestClient.ResponseSpec responseSpec) {
CookieAssertions(ExchangeResult exchangeResult, WebTestClient.ResponseSpec responseSpec) {
super(exchangeResult, responseSpec);
}
@Override
protected void assertWithDiagnostics(Runnable assertion) {
exchangeResult.assertWithDiagnostics(assertion);

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

@ -438,12 +438,12 @@ class DefaultWebTestClient implements WebTestClient { @@ -438,12 +438,12 @@ class DefaultWebTestClient implements WebTestClient {
DefaultResponseSpec(
ExchangeResult exchangeResult, ClientResponse response,
ExchangeResult result, ClientResponse response,
@Nullable JsonEncoderDecoder jsonEncoderDecoder,
Consumer<EntityExchangeResult<?>> entityResultConsumer,
Duration timeout) {
this.exchangeResult = exchangeResult;
this.exchangeResult = result;
this.response = response;
this.jsonEncoderDecoder = jsonEncoderDecoder;
this.entityResultConsumer = entityResultConsumer;
@ -468,15 +468,15 @@ class DefaultWebTestClient implements WebTestClient { @@ -468,15 +468,15 @@ class DefaultWebTestClient implements WebTestClient {
@Override
public <B> BodySpec<B, ?> expectBody(Class<B> bodyType) {
B body = this.response.bodyToMono(bodyType).block(this.timeout);
EntityExchangeResult<B> entityResult = initEntityExchangeResult(body);
return new DefaultBodySpec<>(entityResult);
EntityExchangeResult<B> result = initEntityExchangeResult(body);
return new DefaultBodySpec<>(result);
}
@Override
public <B> BodySpec<B, ?> expectBody(ParameterizedTypeReference<B> bodyType) {
B body = this.response.bodyToMono(bodyType).block(this.timeout);
EntityExchangeResult<B> entityResult = initEntityExchangeResult(body);
return new DefaultBodySpec<>(entityResult);
EntityExchangeResult<B> result = initEntityExchangeResult(body);
return new DefaultBodySpec<>(result);
}
@Override
@ -500,8 +500,8 @@ class DefaultWebTestClient implements WebTestClient { @@ -500,8 +500,8 @@ class DefaultWebTestClient implements WebTestClient {
public BodyContentSpec expectBody() {
ByteArrayResource resource = this.response.bodyToMono(ByteArrayResource.class).block(this.timeout);
byte[] body = (resource != null ? resource.getByteArray() : null);
EntityExchangeResult<byte[]> entityResult = initEntityExchangeResult(body);
return new DefaultBodyContentSpec(entityResult, this.jsonEncoderDecoder);
EntityExchangeResult<byte[]> result = initEntityExchangeResult(body);
return new DefaultBodyContentSpec(result, this.jsonEncoderDecoder);
}
private <B> EntityExchangeResult<B> initEntityExchangeResult(@Nullable B body) {

2
spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java

@ -31,10 +31,12 @@ import org.springframework.test.web.support.AbstractHeaderAssertions; @@ -31,10 +31,12 @@ import org.springframework.test.web.support.AbstractHeaderAssertions;
*/
public class HeaderAssertions extends AbstractHeaderAssertions<ExchangeResult, WebTestClient.ResponseSpec> {
HeaderAssertions(ExchangeResult result, WebTestClient.ResponseSpec spec) {
super(result, spec);
}
@Override
protected void assertWithDiagnostics(Runnable assertion) {
exchangeResult.assertWithDiagnostics(assertion);

5
spring-test/src/main/java/org/springframework/test/web/reactive/server/JsonPathAssertions.java

@ -34,8 +34,11 @@ import org.springframework.test.web.support.AbstractJsonPathAssertions; @@ -34,8 +34,11 @@ import org.springframework.test.web.support.AbstractJsonPathAssertions;
*/
public class JsonPathAssertions extends AbstractJsonPathAssertions<WebTestClient.BodyContentSpec> {
JsonPathAssertions(WebTestClient.BodyContentSpec spec, String content, String expression,
JsonPathAssertions(
WebTestClient.BodyContentSpec spec, String content, String expression,
@Nullable Configuration configuration) {
super(spec, content, expression, configuration);
}
}

2
spring-test/src/main/java/org/springframework/test/web/reactive/server/StatusAssertions.java

@ -29,10 +29,12 @@ import org.springframework.test.web.support.AbstractStatusAssertions; @@ -29,10 +29,12 @@ import org.springframework.test.web.support.AbstractStatusAssertions;
*/
public class StatusAssertions extends AbstractStatusAssertions<ExchangeResult, WebTestClient.ResponseSpec> {
StatusAssertions(ExchangeResult result, WebTestClient.ResponseSpec spec) {
super(result, spec);
}
@Override
protected void assertWithDiagnostics(Runnable assertion) {
exchangeResult.assertWithDiagnostics(assertion);

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

@ -73,7 +73,7 @@ import org.springframework.web.util.UriBuilderFactory; @@ -73,7 +73,7 @@ import org.springframework.web.util.UriBuilderFactory;
* Client for testing web servers that uses {@link WebClient} internally to
* perform requests while also providing a fluent API to verify responses.
* This client can connect to any server over HTTP, or to a WebFlux application
* via mock request and response objects.
* with a mock request and response.
*
* <p>Use one of the bindToXxx methods to create an instance. For example:
* <ul>
@ -89,9 +89,6 @@ import org.springframework.web.util.UriBuilderFactory; @@ -89,9 +89,6 @@ import org.springframework.web.util.UriBuilderFactory;
* @author Sam Brannen
* @author Michał Rowicki
* @since 5.0
* @see StatusAssertions
* @see HeaderAssertions
* @see JsonPathAssertions
*/
public interface WebTestClient {
@ -172,12 +169,10 @@ public interface WebTestClient { @@ -172,12 +169,10 @@ public interface WebTestClient {
/**
* 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
* WebFlux application will be tested without an HTTP server using a mock
* request and response.
* Begin creating a {@link WebTestClient} with a mock server setup that
* tests one {@code @Controller} at a time with
* {@link org.springframework.web.reactive.config.EnableWebFlux @EnableWebFlux}
* equivalent configuration.
* @param controllers one or more controller instances to test
* (specified {@code Class} will be turned into instance)
* @return chained API to customize server and client config; use
@ -188,10 +183,10 @@ public interface WebTestClient { @@ -188,10 +183,10 @@ public interface WebTestClient {
}
/**
* Use this option to set up a server from a {@link RouterFunction}.
* Internally the provided configuration is passed to
* {@code RouterFunctions#toWebHandler}. The resulting WebFlux application
* will be tested without an HTTP server using a mock request and response.
* Begin creating a {@link WebTestClient} with a mock server setup that
* tests one {@code RouterFunction} at a time with
* {@link org.springframework.web.reactive.config.EnableWebFlux @EnableWebFlux}
* equivalent configuration.
* @param routerFunction the RouterFunction to test
* @return chained API to customize server and client config; use
* {@link MockServerSpec#configureClient()} to transition to client config
@ -229,8 +224,7 @@ public interface WebTestClient { @@ -229,8 +224,7 @@ public interface WebTestClient {
}
/**
* This server setup option allows you to connect to a live server through
* a Reactor Netty client connector.
* This server setup option allows you to connect to a live server.
* <p><pre class="code">
* WebTestClient client = WebTestClient.bindToServer()
* .baseUrl("http://localhost:8080")
@ -389,17 +383,12 @@ public interface WebTestClient { @@ -389,17 +383,12 @@ public interface WebTestClient {
/**
* Steps for customizing the {@link WebClient} used to test with,
* internally delegating to a
* {@link org.springframework.web.reactive.function.client.WebClient.Builder
* WebClient.Builder}.
* Steps to customize the underlying {@link WebClient} via {@link WebClient.Builder}.
*/
interface Builder {
/**
* Configure a base URI as described in
* {@link org.springframework.web.reactive.function.client.WebClient#create(String)
* WebClient.create(String)}.
* Configure a base URI as described in {@link WebClient#create(String)}.
*/
Builder baseUrl(String baseUrl);
@ -428,7 +417,7 @@ public interface WebTestClient { @@ -428,7 +417,7 @@ public interface WebTestClient {
Builder defaultHeaders(Consumer<HttpHeaders> headersConsumer);
/**
* Add the given header to all requests that haven't added it.
* Add the given cookie to all requests that haven't already added it.
* @param cookieName the cookie name
* @param cookieValues the cookie values
*/
@ -718,6 +707,7 @@ public interface WebTestClient { @@ -718,6 +707,7 @@ public interface WebTestClient {
* Specification for providing body of a request.
*/
interface RequestBodySpec extends RequestHeadersSpec<RequestBodySpec> {
/**
* Set the length of the body in bytes, as specified by the
* {@code Content-Length} header.
@ -738,7 +728,7 @@ public interface WebTestClient { @@ -738,7 +728,7 @@ public interface WebTestClient {
/**
* Set the body to the given {@code Object} value. This method invokes the
* {@link org.springframework.web.reactive.function.client.WebClient.RequestBodySpec#bodyValue(Object)
* {@link WebClient.RequestBodySpec#bodyValue(Object)
* bodyValue} method on the underlying {@code WebClient}.
* @param body the value to write to the request body
* @return spec for further declaration of the request
@ -773,7 +763,7 @@ public interface WebTestClient { @@ -773,7 +763,7 @@ public interface WebTestClient {
/**
* Set the body from the given producer. This method invokes the
* {@link org.springframework.web.reactive.function.client.WebClient.RequestBodySpec#body(Object, Class)
* {@link WebClient.RequestBodySpec#body(Object, Class)
* body(Object, Class)} method on the underlying {@code WebClient}.
* @param producer the producer to write to the request. This must be a
* {@link Publisher} or another producer adaptable to a
@ -786,7 +776,7 @@ public interface WebTestClient { @@ -786,7 +776,7 @@ public interface WebTestClient {
/**
* Set the body from the given producer. This method invokes the
* {@link org.springframework.web.reactive.function.client.WebClient.RequestBodySpec#body(Object, ParameterizedTypeReference)
* {@link WebClient.RequestBodySpec#body(Object, ParameterizedTypeReference)
* body(Object, ParameterizedTypeReference)} method on the underlying {@code WebClient}.
* @param producer the producer to write to the request. This must be a
* {@link Publisher} or another producer adaptable to a
@ -800,7 +790,7 @@ public interface WebTestClient { @@ -800,7 +790,7 @@ public interface WebTestClient {
/**
* Set the body of the request to the given {@code BodyInserter}.
* This method invokes the
* {@link org.springframework.web.reactive.function.client.WebClient.RequestBodySpec#body(BodyInserter)
* {@link WebClient.RequestBodySpec#body(BodyInserter)
* body(BodyInserter)} method on the underlying {@code WebClient}.
* @param inserter the body inserter to use
* @return spec for further declaration of the request
@ -908,8 +898,8 @@ public interface WebTestClient { @@ -908,8 +898,8 @@ public interface WebTestClient {
BodyContentSpec expectBody();
/**
* Exit the chained flow in order to consume the response body
* externally, for example, via {@link reactor.test.StepVerifier}.
* Exit the chained flow in order to consume the response body externally,
* for example, via {@link reactor.test.StepVerifier}.
* <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

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

@ -28,10 +28,12 @@ import org.springframework.util.MultiValueMap; @@ -28,10 +28,12 @@ import org.springframework.util.MultiValueMap;
*/
public class CookieAssertions extends AbstractCookieAssertions<ExchangeResult, RestTestClient.ResponseSpec> {
public CookieAssertions(ExchangeResult exchangeResult, RestTestClient.ResponseSpec responseSpec) {
CookieAssertions(ExchangeResult exchangeResult, RestTestClient.ResponseSpec responseSpec) {
super(exchangeResult, responseSpec);
}
@Override
protected void assertWithDiagnostics(Runnable assertion) {
exchangeResult.assertWithDiagnostics(assertion);

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

@ -21,7 +21,6 @@ import java.nio.charset.Charset; @@ -21,7 +21,6 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
@ -59,12 +58,9 @@ class DefaultRestTestClient implements RestTestClient { @@ -59,12 +58,9 @@ class DefaultRestTestClient implements RestTestClient {
private final AtomicLong requestIndex = new AtomicLong();
private final RestClient.Builder restClientBuilder;
DefaultRestTestClient(RestClient.Builder restClientBuilder) {
this.restClient = restClientBuilder.build();
this.restClientBuilder = restClientBuilder;
DefaultRestTestClient(RestClient.Builder builder) {
this.restClient = builder.build();
}
@ -104,8 +100,8 @@ class DefaultRestTestClient implements RestTestClient { @@ -104,8 +100,8 @@ class DefaultRestTestClient implements RestTestClient {
}
@Override
public RequestBodyUriSpec method(HttpMethod method) {
return methodInternal(method);
public RequestBodyUriSpec method(HttpMethod httpMethod) {
return methodInternal(httpMethod);
}
private RequestBodyUriSpec methodInternal(HttpMethod httpMethod) {
@ -114,7 +110,7 @@ class DefaultRestTestClient implements RestTestClient { @@ -114,7 +110,7 @@ class DefaultRestTestClient implements RestTestClient {
@Override
public <B extends Builder<B>> Builder<B> mutate() {
return new DefaultRestTestClientBuilder<>(this.restClientBuilder);
return new DefaultRestTestClientBuilder<>(this.restClient.mutate());
}
@ -122,103 +118,105 @@ class DefaultRestTestClient implements RestTestClient { @@ -122,103 +118,105 @@ class DefaultRestTestClient implements RestTestClient {
private final RestClient.RequestBodyUriSpec requestHeadersUriSpec;
private RestClient.RequestBodySpec requestBodySpec;
private final String requestId;
DefaultRequestBodyUriSpec(RestClient.RequestBodyUriSpec spec) {
this.requestHeadersUriSpec = spec;
this.requestBodySpec = spec;
this.requestId = String.valueOf(requestIndex.incrementAndGet());
String requestId = String.valueOf(requestIndex.incrementAndGet());
this.requestHeadersUriSpec.header(RESTTESTCLIENT_REQUEST_ID, requestId);
}
@Override
public RequestBodySpec uri(String uriTemplate, Object... uriVariables) {
this.requestBodySpec = this.requestHeadersUriSpec.uri(uriTemplate, uriVariables);
public RequestBodySpec uri(String uriTemplate, @Nullable Object... uriVariables) {
this.requestHeadersUriSpec.uri(uriTemplate, uriVariables);
return this;
}
@Override
public RequestBodySpec uri(String uri, Map<String, ?> uriVariables) {
this.requestBodySpec = this.requestHeadersUriSpec.uri(uri, uriVariables);
this.requestHeadersUriSpec.uri(uri, uriVariables);
return this;
}
@Override
public RequestBodySpec uri(Function<UriBuilder, URI> uriFunction) {
this.requestBodySpec = this.requestHeadersUriSpec.uri(uriFunction);
this.requestHeadersUriSpec.uri(uriFunction);
return this;
}
@Override
public RequestBodySpec uri(URI uri) {
this.requestBodySpec = this.requestHeadersUriSpec.uri(uri);
this.requestHeadersUriSpec.uri(uri);
return this;
}
@Override
public RequestBodySpec header(String headerName, String... headerValues) {
this.requestBodySpec = this.requestHeadersUriSpec.header(headerName, headerValues);
this.requestHeadersUriSpec.header(headerName, headerValues);
return this;
}
@Override
public RequestBodySpec headers(Consumer<HttpHeaders> headersConsumer) {
this.requestBodySpec = this.requestHeadersUriSpec.headers(headersConsumer);
this.requestHeadersUriSpec.headers(headersConsumer);
return this;
}
@Override
public RequestBodySpec accept(MediaType... acceptableMediaTypes) {
this.requestBodySpec = this.requestHeadersUriSpec.accept(acceptableMediaTypes);
this.requestHeadersUriSpec.accept(acceptableMediaTypes);
return this;
}
@Override
public RequestBodySpec acceptCharset(Charset... acceptableCharsets) {
this.requestBodySpec = this.requestHeadersUriSpec.acceptCharset(acceptableCharsets);
this.requestHeadersUriSpec.acceptCharset(acceptableCharsets);
return this;
}
@Override
public RequestBodySpec contentType(MediaType contentType) {
this.requestBodySpec = this.requestHeadersUriSpec.contentType(contentType);
this.requestHeadersUriSpec.contentType(contentType);
return this;
}
@Override
public RequestBodySpec contentLength(long contentLength) {
this.requestHeadersUriSpec.contentLength(contentLength);
return this;
}
@Override
public RequestBodySpec cookie(String name, String value) {
this.requestBodySpec = this.requestHeadersUriSpec.cookie(name, value);
this.requestHeadersUriSpec.cookie(name, value);
return this;
}
@Override
public RequestBodySpec cookies(Consumer<MultiValueMap<String, String>> cookiesConsumer) {
this.requestBodySpec = this.requestHeadersUriSpec.cookies(cookiesConsumer);
this.requestHeadersUriSpec.cookies(cookiesConsumer);
return this;
}
@Override
public RequestBodySpec ifModifiedSince(ZonedDateTime ifModifiedSince) {
this.requestBodySpec = this.requestHeadersUriSpec.ifModifiedSince(ifModifiedSince);
this.requestHeadersUriSpec.ifModifiedSince(ifModifiedSince);
return this;
}
@Override
public RequestBodySpec ifNoneMatch(String... ifNoneMatches) {
this.requestBodySpec = this.requestHeadersUriSpec.ifNoneMatch(ifNoneMatches);
this.requestHeadersUriSpec.ifNoneMatch(ifNoneMatches);
return this;
}
@Override
public RequestBodySpec attribute(String name, Object value) {
this.requestBodySpec = this.requestHeadersUriSpec.attribute(name, value);
this.requestHeadersUriSpec.attribute(name, value);
return this;
}
@Override
public RequestBodySpec attributes(Consumer<Map<String, Object>> attributesConsumer) {
this.requestBodySpec = this.requestHeadersUriSpec.attributes(attributesConsumer);
this.requestHeadersUriSpec.attributes(attributesConsumer);
return this;
}
@ -230,11 +228,9 @@ class DefaultRestTestClient implements RestTestClient { @@ -230,11 +228,9 @@ class DefaultRestTestClient implements RestTestClient {
@Override
public ResponseSpec exchange() {
this.requestBodySpec = this.requestBodySpec.header(RESTTESTCLIENT_REQUEST_ID, this.requestId);
ExchangeResult exchangeResult = this.requestBodySpec.exchange(
(clientRequest, clientResponse) -> new ExchangeResult(clientResponse),
false);
return new DefaultResponseSpec(Objects.requireNonNull(exchangeResult));
return new DefaultResponseSpec(
this.requestHeadersUriSpec.exchangeForRequiredValue(
(request, response) -> new ExchangeResult(response), false));
}
}
@ -243,8 +239,8 @@ class DefaultRestTestClient implements RestTestClient { @@ -243,8 +239,8 @@ class DefaultRestTestClient implements RestTestClient {
private final ExchangeResult exchangeResult;
DefaultResponseSpec(ExchangeResult exchangeResult) {
this.exchangeResult = exchangeResult;
DefaultResponseSpec(ExchangeResult result) {
this.exchangeResult = result;
}
@Override
@ -265,19 +261,22 @@ class DefaultRestTestClient implements RestTestClient { @@ -265,19 +261,22 @@ class DefaultRestTestClient implements RestTestClient {
@Override
public <B> BodySpec<B, ?> expectBody(Class<B> bodyType) {
B body = this.exchangeResult.getBody(bodyType);
return new DefaultBodySpec<>(new EntityExchangeResult<>(this.exchangeResult, body));
EntityExchangeResult<B> result = new EntityExchangeResult<>(this.exchangeResult, body);
return new DefaultBodySpec<>(result);
}
@Override
public <B> BodySpec<B, ?> expectBody(ParameterizedTypeReference<B> bodyType) {
B body = this.exchangeResult.getBody(bodyType);
return new DefaultBodySpec<>(new EntityExchangeResult<>(this.exchangeResult, body));
EntityExchangeResult<B> result = new EntityExchangeResult<>(this.exchangeResult, body);
return new DefaultBodySpec<>(result);
}
@Override
public BodyContentSpec expectBody() {
byte[] body = this.exchangeResult.getBody(byte[].class);
return new DefaultBodyContentSpec( new EntityExchangeResult<>(this.exchangeResult, body));
EntityExchangeResult<byte[]> result = new EntityExchangeResult<>(this.exchangeResult, body);
return new DefaultBodyContentSpec(result);
}
@Override
@ -318,20 +317,26 @@ class DefaultRestTestClient implements RestTestClient { @@ -318,20 +317,26 @@ class DefaultRestTestClient implements RestTestClient {
private final EntityExchangeResult<B> result;
DefaultBodySpec(@Nullable EntityExchangeResult<B> result) {
this.result = Objects.requireNonNull(result, "exchangeResult must be non-null");
DefaultBodySpec(EntityExchangeResult<B> result) {
this.result = result;
}
@Override
public <T extends S> T isEqualTo(B expected) {
public <T extends S> T isEqualTo(@Nullable B expected) {
this.result.assertWithDiagnostics(() ->
AssertionErrors.assertEquals("Response body", expected, this.result.getResponseBody()));
return self();
}
@Override
public <T extends S> T value(Matcher<? super @Nullable B> matcher) {
this.result.assertWithDiagnostics(() -> MatcherAssert.assertThat(this.result.getResponseBody(), matcher));
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) {
public <T extends S, R> T value(Function<@Nullable B, @Nullable R> bodyMapper, Matcher<? super @Nullable R> matcher) {
this.result.assertWithDiagnostics(() -> {
B body = this.result.getResponseBody();
MatcherAssert.assertThat(bodyMapper.apply(body), matcher);
@ -340,7 +345,8 @@ class DefaultRestTestClient implements RestTestClient { @@ -340,7 +345,8 @@ class DefaultRestTestClient implements RestTestClient {
}
@Override
public <T extends S> T value(Consumer<B> consumer) {
@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1129
public <T extends S> T value(Consumer<@Nullable B> consumer) {
this.result.assertWithDiagnostics(() -> consumer.accept(this.result.getResponseBody()));
return self();
}
@ -374,8 +380,7 @@ class DefaultRestTestClient implements RestTestClient { @@ -374,8 +380,7 @@ class DefaultRestTestClient implements RestTestClient {
@Override
public EntityExchangeResult<Void> isEmpty() {
this.result.assertWithDiagnostics(() ->
AssertionErrors.assertTrue("Expected empty body",
this.result.getBody(byte[].class) == null));
AssertionErrors.assertTrue("Expected empty body", this.result.getBody(byte[].class) == null));
return new EntityExchangeResult<>(this.result, null);
}

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

@ -18,12 +18,14 @@ package org.springframework.test.web.servlet.client; @@ -18,12 +18,14 @@ package org.springframework.test.web.servlet.client;
import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MockMvcBuilder;
import org.springframework.test.web.servlet.client.RestTestClient.MockMvcSetupBuilder;
import org.springframework.test.web.servlet.client.RestTestClient.RouterFunctionSetupBuilder;
import org.springframework.test.web.servlet.client.RestTestClient.StandaloneSetupBuilder;
import org.springframework.test.web.servlet.client.RestTestClient.WebAppContextSetupBuilder;
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.test.web.servlet.setup.RouterFunctionMockMvcBuilder;
@ -39,8 +41,8 @@ import org.springframework.web.util.UriBuilderFactory; @@ -39,8 +41,8 @@ import org.springframework.web.util.UriBuilderFactory;
*
* @author Rob Worsnop
* @author Rossen Stoyanchev
* @param <B> the type of the builder
* @since 7.0
* @param <B> the type of the builder
*/
class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implements RestTestClient.Builder<B> {
@ -92,12 +94,6 @@ class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implemen @@ -92,12 +94,6 @@ class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implemen
return self();
}
@Override
public <T extends B> T apply(Consumer<RestTestClient.Builder<B>> builderConsumer) {
builderConsumer.accept(this);
return self();
}
@SuppressWarnings("unchecked")
protected <T extends B> T self() {
return (T) this;
@ -113,8 +109,13 @@ class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implemen @@ -113,8 +109,13 @@ class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implemen
}
/**
* Base class for implementations for {@link MockMvcSetupBuilder}.
* @param <S> the "self" type of the builder
* @param <M> the type of {@link MockMvc} builder
*/
static class AbstractMockMvcSetupBuilder<S extends RestTestClient.Builder<S>, M extends MockMvcBuilder>
extends DefaultRestTestClientBuilder<S> implements RestTestClient.MockMvcSetupBuilder<S, M> {
extends DefaultRestTestClientBuilder<S> implements MockMvcSetupBuilder<S, M> {
private final M mockMvcBuilder;
@ -136,8 +137,12 @@ class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implemen @@ -136,8 +137,12 @@ class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implemen
}
static class DefaultStandaloneSetupBuilder extends AbstractMockMvcSetupBuilder<RestTestClient.StandaloneSetupBuilder, StandaloneMockMvcBuilder>
implements RestTestClient.StandaloneSetupBuilder {
/**
* Default implementation of {@link StandaloneSetupBuilder}.
*/
static class DefaultStandaloneSetupBuilder
extends AbstractMockMvcSetupBuilder<StandaloneSetupBuilder, StandaloneMockMvcBuilder>
implements StandaloneSetupBuilder {
DefaultStandaloneSetupBuilder(Object... controllers) {
super(MockMvcBuilders.standaloneSetup(controllers));
@ -145,8 +150,12 @@ class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implemen @@ -145,8 +150,12 @@ class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implemen
}
static class DefaultRouterFunctionSetupBuilder extends AbstractMockMvcSetupBuilder<RestTestClient.RouterFunctionSetupBuilder, RouterFunctionMockMvcBuilder>
implements RestTestClient.RouterFunctionSetupBuilder {
/**
* Default implementation of {@link RouterFunctionSetupBuilder}.
*/
static class DefaultRouterFunctionSetupBuilder
extends AbstractMockMvcSetupBuilder<RouterFunctionSetupBuilder, RouterFunctionMockMvcBuilder>
implements RouterFunctionSetupBuilder {
DefaultRouterFunctionSetupBuilder(RouterFunction<?>... routerFunctions) {
super(MockMvcBuilders.routerFunctions(routerFunctions));
@ -155,8 +164,12 @@ class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implemen @@ -155,8 +164,12 @@ class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implemen
}
static class DefaultWebAppContextSetupBuilder extends AbstractMockMvcSetupBuilder<RestTestClient.WebAppContextSetupBuilder, DefaultMockMvcBuilder>
implements RestTestClient.WebAppContextSetupBuilder {
/**
* Default implementation of {@link WebAppContextSetupBuilder}.
*/
static class DefaultWebAppContextSetupBuilder
extends AbstractMockMvcSetupBuilder<WebAppContextSetupBuilder, DefaultMockMvcBuilder>
implements WebAppContextSetupBuilder {
DefaultWebAppContextSetupBuilder(WebApplicationContext context) {
super(MockMvcBuilders.webAppContextSetup(context));

78
spring-test/src/main/java/org/springframework/test/web/servlet/client/ExchangeResult.java

@ -19,7 +19,6 @@ package org.springframework.test.web.servlet.client; @@ -19,7 +19,6 @@ package org.springframework.test.web.servlet.client;
import java.io.IOException;
import java.net.HttpCookie;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -32,6 +31,7 @@ import org.springframework.core.ParameterizedTypeReference; @@ -32,6 +31,7 @@ import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseCookie;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClient.RequestHeadersSpec.ConvertibleClientHttpResponse;
@ -41,21 +41,28 @@ import org.springframework.web.client.RestClient.RequestHeadersSpec.ConvertibleC @@ -41,21 +41,28 @@ import org.springframework.web.client.RestClient.RequestHeadersSpec.ConvertibleC
* {@link RestTestClient}.
*
* @author Rob Worsnop
* @author Rossen Stoyanchev
* @since 7.0
*/
public class ExchangeResult {
private static final Pattern SAME_SITE_PATTERN = Pattern.compile("(?i).*SameSite=(Strict|Lax|None).*");
private static final Pattern PARTITIONED_PATTERN = Pattern.compile("(?i).*;\\s*Partitioned(\\s*;.*|\\s*)$");
private static final Log logger = LogFactory.getLog(ExchangeResult.class);
private final ConvertibleClientHttpResponse clientResponse;
/** Ensure single logging; for example, for expectAll. */
private boolean diagnosticsLogged;
private final ConvertibleClientHttpResponse clientResponse;
ExchangeResult(@Nullable ConvertibleClientHttpResponse clientResponse) {
this.clientResponse = Objects.requireNonNull(clientResponse, "clientResponse must be non-null");
ExchangeResult(@Nullable ConvertibleClientHttpResponse response) {
Assert.notNull(response, "Response must not be null");
this.clientResponse = response;
}
ExchangeResult(ExchangeResult result) {
@ -63,6 +70,10 @@ public class ExchangeResult { @@ -63,6 +70,10 @@ public class ExchangeResult {
this.diagnosticsLogged = result.diagnosticsLogged;
}
/**
* Return the HTTP status code as an {@link HttpStatusCode} value.
*/
public HttpStatusCode getStatus() {
try {
return this.clientResponse.getStatusCode();
@ -72,10 +83,41 @@ public class ExchangeResult { @@ -72,10 +83,41 @@ public class ExchangeResult {
}
}
/**
* Return the response headers received from the server.
*/
public HttpHeaders getResponseHeaders() {
return this.clientResponse.getHeaders();
}
/**
* Return response cookies received from the server.
*/
public MultiValueMap<String, ResponseCookie> getResponseCookies() {
return Optional.ofNullable(this.clientResponse.getHeaders().get(HttpHeaders.SET_COOKIE)).orElse(List.of()).stream()
.flatMap(header -> {
Matcher matcher = SAME_SITE_PATTERN.matcher(header);
String sameSite = (matcher.matches() ? matcher.group(1) : null);
boolean partitioned = PARTITIONED_PATTERN.matcher(header).matches();
return HttpCookie.parse(header).stream().map(cookie -> toResponseCookie(cookie, sameSite, partitioned));
})
.collect(LinkedMultiValueMap::new,
(cookies, cookie) -> cookies.add(cookie.getName(), cookie),
LinkedMultiValueMap::addAll);
}
private static ResponseCookie toResponseCookie(HttpCookie cookie, @Nullable String sameSite, boolean partitioned) {
return ResponseCookie.from(cookie.getName(), cookie.getValue())
.domain(cookie.getDomain())
.httpOnly(cookie.isHttpOnly())
.maxAge(cookie.getMaxAge())
.path(cookie.getPath())
.secure(cookie.getSecure())
.sameSite(sameSite)
.partitioned(partitioned)
.build();
}
@Nullable
public <T> T getBody(Class<T> bodyType) {
return this.clientResponse.bodyTo(bodyType);
@ -86,7 +128,6 @@ public class ExchangeResult { @@ -86,7 +128,6 @@ public class ExchangeResult {
return this.clientResponse.bodyTo(bodyType);
}
/**
* Execute the given Runnable, catch any {@link AssertionError}, log details
* about the request and response at ERROR level under the class log
@ -105,31 +146,4 @@ public class ExchangeResult { @@ -105,31 +146,4 @@ public class ExchangeResult {
}
}
/**
* Return response cookies received from the server.
*/
public MultiValueMap<String, ResponseCookie> getResponseCookies() {
return Optional.ofNullable(this.clientResponse.getHeaders().get(HttpHeaders.SET_COOKIE)).orElse(List.of()).stream()
.flatMap(header -> {
Matcher matcher = SAME_SITE_PATTERN.matcher(header);
String sameSite = (matcher.matches() ? matcher.group(1) : null);
boolean partitioned = PARTITIONED_PATTERN.matcher(header).matches();
return HttpCookie.parse(header).stream().map(cookie -> toResponseCookie(cookie, sameSite, partitioned));
})
.collect(LinkedMultiValueMap::new,
(cookies, cookie) -> cookies.add(cookie.getName(), cookie),
LinkedMultiValueMap::addAll);
}
private static ResponseCookie toResponseCookie(HttpCookie cookie, @Nullable String sameSite, boolean partitioned) {
return ResponseCookie.from(cookie.getName(), cookie.getValue())
.domain(cookie.getDomain())
.httpOnly(cookie.isHttpOnly())
.maxAge(cookie.getMaxAge())
.path(cookie.getPath())
.secure(cookie.getSecure())
.sameSite(sameSite)
.partitioned(partitioned)
.build();
}
}

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

@ -29,7 +29,7 @@ import org.springframework.test.web.support.AbstractHeaderAssertions; @@ -29,7 +29,7 @@ import org.springframework.test.web.support.AbstractHeaderAssertions;
public class HeaderAssertions extends AbstractHeaderAssertions<ExchangeResult, RestTestClient.ResponseSpec> {
public HeaderAssertions(ExchangeResult exchangeResult, RestTestClient.ResponseSpec responseSpec) {
HeaderAssertions(ExchangeResult exchangeResult, RestTestClient.ResponseSpec responseSpec) {
super(exchangeResult, responseSpec);
}

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

@ -26,13 +26,19 @@ import org.springframework.test.web.support.AbstractJsonPathAssertions; @@ -26,13 +26,19 @@ import org.springframework.test.web.support.AbstractJsonPathAssertions;
* <a href="https://github.com/jayway/JsonPath">JsonPath</a> assertions.
*
* @author Rob Worsnop
* @author Rossen Stoyanchev
* @since 7.0
* @see <a href="https://github.com/jayway/JsonPath">https://github.com/jayway/JsonPath</a>
* @see JsonPathExpectationsHelper
*/
public class JsonPathAssertions extends AbstractJsonPathAssertions<RestTestClient.BodyContentSpec> {
JsonPathAssertions(RestTestClient.BodyContentSpec spec, String content, String expression, @Nullable Configuration configuration) {
JsonPathAssertions(
RestTestClient.BodyContentSpec spec, String content, String expression,
@Nullable Configuration configuration) {
super(spec, content, expression, configuration);
}
}

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

@ -37,6 +37,7 @@ import org.springframework.test.json.JsonComparison; @@ -37,6 +37,7 @@ import org.springframework.test.json.JsonComparison;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MockMvcBuilder;
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.test.web.servlet.setup.RouterFunctionMockMvcBuilder;
import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder;
import org.springframework.util.MultiValueMap;
@ -47,7 +48,19 @@ import org.springframework.web.util.UriBuilder; @@ -47,7 +48,19 @@ import org.springframework.web.util.UriBuilder;
import org.springframework.web.util.UriBuilderFactory;
/**
* Client for testing web servers.
* Client for testing web servers that uses {@link RestClient} internally to
* perform requests while also providing a fluent API to verify responses.
* This client can connect to any server over HTTP or to a {@link MockMvc} server
* with a mock request and response.
*
* <p>Use one of the bindToXxx methods to create an instance. For example:
* <ul>
* <li>{@link #bindToController(Object...)}
* <li>{@link #bindToRouterFunction(RouterFunction[])}
* <li>{@link #bindToApplicationContext(WebApplicationContext)}
* <li>{@link #bindToServer()}
* <li>...
* </ul>
*
* @author Rob Worsnop
* @author Rossen Stoyanchev
@ -121,34 +134,24 @@ public interface RestTestClient { @@ -121,34 +134,24 @@ public interface RestTestClient {
/**
* Begin creating a {@link RestTestClient} 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}.
* Begin creating a {@link RestTestClient} with a {@link MockMvcBuilders#standaloneSetup
* Standalone MockMvc setup}.
*/
static StandaloneSetupBuilder bindToController(Object... controllers) {
return new DefaultRestTestClientBuilder.DefaultStandaloneSetupBuilder(controllers);
}
/**
* Begin creating a {@link RestTestClient} by providing the {@link RouterFunction}
* instance(s) to handle requests with.
* <p>Internally this is delegated to and equivalent to using
* {@link org.springframework.test.web.servlet.setup.MockMvcBuilders#routerFunctions(RouterFunction[])}
* to initialize {@link MockMvc}.
* Begin creating a {@link RestTestClient} with a {@link MockMvcBuilders#routerFunctions}
* RouterFunction's MockMvc setup}.
*/
static RouterFunctionSetupBuilder bindToRouterFunction(RouterFunction<?>... routerFunctions) {
return new DefaultRestTestClientBuilder.DefaultRouterFunctionSetupBuilder(routerFunctions);
}
/**
* Begin creating a {@link RestTestClient} 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}.
* Begin creating a {@link RestTestClient} with a {@link MockMvcBuilders#webAppContextSetup}
* WebAppContext MockMvc setup}.
*/
static WebAppContextSetupBuilder bindToApplicationContext(WebApplicationContext context) {
return new DefaultRestTestClientBuilder.DefaultWebAppContextSetupBuilder(context);
@ -164,8 +167,7 @@ public interface RestTestClient { @@ -164,8 +167,7 @@ public interface RestTestClient {
}
/**
* This server setup option allows you to connect to a live server through
* a client connector.
* This server setup option allows you to connect to a live server.
* <p><pre class="code">
* RestTestClient client = RestTestClient.bindToServer()
* .baseUrl("http://localhost:8080")
@ -186,12 +188,14 @@ public interface RestTestClient { @@ -186,12 +188,14 @@ public interface RestTestClient {
}
/**
* Steps to customize the underlying {@link RestClient} via {@link RestClient.Builder}.
* @param <B> the type of builder
*/
interface Builder<B extends Builder<B>> {
/**
* Configure a base URI as described in
* {@link RestClient#create(String)
* WebClient.create(String)}.
* Configure a base URI as described in {@link RestClient#create(String)}.
*/
<T extends B> T baseUrl(String baseUrl);
@ -203,7 +207,7 @@ public interface RestTestClient { @@ -203,7 +207,7 @@ public interface RestTestClient {
/**
* Add the given header to all requests that haven't added it.
* @param headerName the header name
* @param headerName the header name
* @param headerValues the header values
*/
<T extends B> T defaultHeader(String headerName, String... headerValues);
@ -220,8 +224,8 @@ public interface RestTestClient { @@ -220,8 +224,8 @@ public interface RestTestClient {
<T extends B> T defaultHeaders(Consumer<HttpHeaders> headersConsumer);
/**
* Add the given cookie to all requests.
* @param cookieName the cookie name
* Add the given cookie to all requests that haven't already added it.
* @param cookieName the cookie name
* @param cookieValues the cookie values
*/
<T extends B> T defaultCookie(String cookieName, String... cookieValues);
@ -237,39 +241,48 @@ public interface RestTestClient { @@ -237,39 +241,48 @@ public interface RestTestClient {
*/
<T extends B> T defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
/**
* 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 extends B> T apply(Consumer<Builder<B>> builderConsumer);
/**
* Build the {@link RestTestClient} instance.
*/
RestTestClient build();
}
}
interface MockMvcSetupBuilder<B extends Builder<B>, M extends MockMvcBuilder> extends Builder<B> {
/**
* Extension of {@link Builder} for tests against a MockMvc server.
* @param <S> the builder type
* @param <M> the type of {@link MockMvc} setup
*/
interface MockMvcSetupBuilder<S extends Builder<S>, M extends MockMvcBuilder> extends Builder<S> {
<T extends B> T configureServer(Consumer<M> consumer);
<T extends S> T configureServer(Consumer<M> consumer);
}
/**
* Extension of {@link Builder} for tests витх а
* {@link MockMvcBuilders#standaloneSetup(Object...) standalone MockMvc setup}.
*/
interface StandaloneSetupBuilder extends MockMvcSetupBuilder<StandaloneSetupBuilder, StandaloneMockMvcBuilder> {
}
/**
* Extension of {@link Builder} for tests витх а
* {@link MockMvcBuilders#routerFunctions(RouterFunction[]) RouterFunction MockMvc setup}.
*/
interface RouterFunctionSetupBuilder extends MockMvcSetupBuilder<RouterFunctionSetupBuilder, RouterFunctionMockMvcBuilder> {
}
/**
* Extension of {@link Builder} for tests витх а
* {@link MockMvcBuilders#webAppContextSetup(WebApplicationContext) WebAppContext MockMvc setup}.
*/
interface WebAppContextSetupBuilder extends MockMvcSetupBuilder<WebAppContextSetupBuilder, DefaultMockMvcBuilder> {
}
/**
* Specification for providing the URI of a request.
*
@ -294,7 +307,7 @@ public interface RestTestClient { @@ -294,7 +307,7 @@ public interface RestTestClient {
* with a base URI) it will be used to expand the URI template.
* @return spec to add headers or perform the exchange
*/
S uri(String uri, Object... uriVariables);
S uri(String uri, @Nullable Object... uriVariables);
/**
* Specify the URI for the request using a URI template and URI variables.
@ -302,7 +315,7 @@ public interface RestTestClient { @@ -302,7 +315,7 @@ public interface RestTestClient {
* with a base URI) it will be used to expand the URI template.
* @return spec to add headers or perform the exchange
*/
S uri(String uri, Map<String, ?> uriVariables);
S uri(String uri, Map<String, ? extends @Nullable Object> uriVariables);
/**
* Build the URI for the request with a {@link UriBuilder} obtained
@ -419,6 +432,16 @@ public interface RestTestClient { @@ -419,6 +432,16 @@ public interface RestTestClient {
* Specification for providing body of a request.
*/
interface RequestBodySpec extends RequestHeadersSpec<RequestBodySpec> {
/**
* Set the length of the body in bytes, as specified by the
* {@code Content-Length} header.
* @param contentLength the content length
* @return the same instance
* @see HttpHeaders#setContentLength(long)
*/
RequestBodySpec contentLength(long contentLength);
/**
* Set the {@linkplain MediaType media type} of the body, as specified
* by the {@code Content-Type} header.
@ -430,7 +453,7 @@ public interface RestTestClient { @@ -430,7 +453,7 @@ public interface RestTestClient {
/**
* Set the body to the given {@code Object} value. This method invokes the
* {@link org.springframework.web.client.RestClient.RequestBodySpec#body(Object)} (Object)
* {@link RestClient.RequestBodySpec#body(Object)} (Object)
* bodyValue} method on the underlying {@code RestClient}.
* @param body the value to write to the request body
* @return spec for further declaration of the request
@ -461,8 +484,8 @@ public interface RestTestClient { @@ -461,8 +484,8 @@ public interface RestTestClient {
interface ResponseSpec {
/**
* Apply multiple assertions to a response with the given
* {@linkplain RestTestClient.ResponseSpec.ResponseSpecConsumer consumers}, with the guarantee that
s * Apply multiple assertions to a response with the given
* {@linkplain 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,
@ -522,8 +545,7 @@ public interface RestTestClient { @@ -522,8 +545,7 @@ public interface RestTestClient {
BodyContentSpec expectBody();
/**
* Exit the chained flow in order to consume the response body
* externally.
* Exit the chained flow in order to consume the response body externally.
*/
<T> EntityExchangeResult<T> returnResult(Class<T> elementClass);
@ -534,8 +556,8 @@ public interface RestTestClient { @@ -534,8 +556,8 @@ public interface RestTestClient {
<T> EntityExchangeResult<T> returnResult(ParameterizedTypeReference<T> elementTypeRef);
/**
* {@link Consumer} of a {@link RestTestClient.ResponseSpec}.
* @see RestTestClient.ResponseSpec#expectAll(RestTestClient.ResponseSpec.ResponseSpecConsumer...)
* {@link Consumer} of a {@link ResponseSpec}.
* @see ResponseSpec#expectAll(ResponseSpecConsumer...)
*/
@FunctionalInterface
interface ResponseSpecConsumer extends Consumer<ResponseSpec> {
@ -554,18 +576,24 @@ public interface RestTestClient { @@ -554,18 +576,24 @@ public interface RestTestClient {
/**
* Assert the extracted body is equal to the given value.
*/
<T extends S> T isEqualTo(B expected);
<T extends S> T isEqualTo(@Nullable B expected);
/**
* Assert the extracted body with a {@link Matcher}.
* @since 5.1
*/
<T extends S> T value(Matcher<? super @Nullable B> matcher);
/**
* 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);
<T extends S, R> T value(Function<@Nullable B, @Nullable R> bodyMapper, Matcher<? super @Nullable R> matcher);
/**
* Assert the extracted body with a {@link Consumer}.
*/
<T extends S> T value(Consumer<B> consumer);
<T extends S> T value(Consumer<@Nullable B> consumer);
/**
* Assert the exchange result with the given {@link Consumer}.

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

@ -29,10 +29,12 @@ import org.springframework.test.web.support.AbstractStatusAssertions; @@ -29,10 +29,12 @@ import org.springframework.test.web.support.AbstractStatusAssertions;
*/
public class StatusAssertions extends AbstractStatusAssertions<ExchangeResult, RestTestClient.ResponseSpec> {
public StatusAssertions(ExchangeResult exchangeResult, ResponseSpec responseSpec) {
StatusAssertions(ExchangeResult exchangeResult, ResponseSpec responseSpec) {
super(exchangeResult, responseSpec);
}
@Override
protected void assertWithDiagnostics(Runnable assertion) {
exchangeResult.assertWithDiagnostics(assertion);

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

@ -33,11 +33,15 @@ import org.springframework.util.Assert; @@ -33,11 +33,15 @@ import org.springframework.util.Assert;
*/
public class XpathAssertions extends AbstractXpathAssertions<RestTestClient.BodyContentSpec> {
XpathAssertions(RestTestClient.BodyContentSpec spec,
String expression, @Nullable Map<String, String> namespaces, Object... args) {
XpathAssertions(
RestTestClient.BodyContentSpec spec,
String expression, @Nullable Map<String, String> namespaces, Object... args) {
super(spec, expression, namespaces, args);
}
@Override
protected Optional<HttpHeaders> getResponseHeaders() {
return Optional.of(bodySpec.returnResult())

2
spring-test/src/test/java/org/springframework/test/web/servlet/client/samples/RestTestClientTests.java

@ -133,7 +133,7 @@ class RestTestClientTests { @@ -133,7 +133,7 @@ class RestTestClientTests {
@Test
void test() {
RestTestClientTests.this.client.mutate()
.apply(builder -> builder.defaultHeader("foo", "bar"))
.defaultHeader("foo", "bar")
.uriBuilderFactory(new DefaultUriBuilderFactory("/test"))
.defaultCookie("foo", "bar")
.defaultCookies(cookies -> cookies.add("a", "b"))

Loading…
Cancel
Save