From 34f259778e23265c6f5c5a31cde02de3e69fcc67 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 29 Jul 2025 19:07:16 +0100 Subject: [PATCH] Further alignment of RestTestClient and WebTestClient See gh-34428 --- .../web/reactive/server/CookieAssertions.java | 4 +- .../reactive/server/DefaultWebTestClient.java | 16 +-- .../web/reactive/server/HeaderAssertions.java | 2 + .../reactive/server/JsonPathAssertions.java | 5 +- .../web/reactive/server/StatusAssertions.java | 2 + .../web/reactive/server/WebTestClient.java | 50 +++---- .../web/servlet/client/CookieAssertions.java | 4 +- .../servlet/client/DefaultRestTestClient.java | 101 +++++++------- .../client/DefaultRestTestClientBuilder.java | 45 ++++--- .../web/servlet/client/ExchangeResult.java | 78 ++++++----- .../web/servlet/client/HeaderAssertions.java | 2 +- .../servlet/client/JsonPathAssertions.java | 8 +- .../web/servlet/client/RestTestClient.java | 124 +++++++++++------- .../web/servlet/client/StatusAssertions.java | 4 +- .../web/servlet/client/XpathAssertions.java | 8 +- .../client/samples/RestTestClientTests.java | 2 +- 16 files changed, 264 insertions(+), 191 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/CookieAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/CookieAssertions.java index 6deca1e015b..dd47cec18bb 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/CookieAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/CookieAssertions.java @@ -29,10 +29,12 @@ import org.springframework.util.MultiValueMap; */ public class CookieAssertions extends AbstractCookieAssertions { - 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); diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java index 0659241cae2..78e287b4248 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java @@ -438,12 +438,12 @@ class DefaultWebTestClient implements WebTestClient { DefaultResponseSpec( - ExchangeResult exchangeResult, ClientResponse response, + ExchangeResult result, ClientResponse response, @Nullable JsonEncoderDecoder jsonEncoderDecoder, Consumer> 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 { @Override public BodySpec expectBody(Class bodyType) { B body = this.response.bodyToMono(bodyType).block(this.timeout); - EntityExchangeResult entityResult = initEntityExchangeResult(body); - return new DefaultBodySpec<>(entityResult); + EntityExchangeResult result = initEntityExchangeResult(body); + return new DefaultBodySpec<>(result); } @Override public BodySpec expectBody(ParameterizedTypeReference bodyType) { B body = this.response.bodyToMono(bodyType).block(this.timeout); - EntityExchangeResult entityResult = initEntityExchangeResult(body); - return new DefaultBodySpec<>(entityResult); + EntityExchangeResult result = initEntityExchangeResult(body); + return new DefaultBodySpec<>(result); } @Override @@ -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 entityResult = initEntityExchangeResult(body); - return new DefaultBodyContentSpec(entityResult, this.jsonEncoderDecoder); + EntityExchangeResult result = initEntityExchangeResult(body); + return new DefaultBodyContentSpec(result, this.jsonEncoderDecoder); } private EntityExchangeResult initEntityExchangeResult(@Nullable B body) { diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java index 5cf42730b57..d8ca7dadab9 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java @@ -31,10 +31,12 @@ import org.springframework.test.web.support.AbstractHeaderAssertions; */ public class HeaderAssertions extends AbstractHeaderAssertions { + HeaderAssertions(ExchangeResult result, WebTestClient.ResponseSpec spec) { super(result, spec); } + @Override protected void assertWithDiagnostics(Runnable assertion) { exchangeResult.assertWithDiagnostics(assertion); diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/JsonPathAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/JsonPathAssertions.java index 5e5e5da7899..44602c185a9 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/JsonPathAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/JsonPathAssertions.java @@ -34,8 +34,11 @@ import org.springframework.test.web.support.AbstractJsonPathAssertions; */ public class JsonPathAssertions extends AbstractJsonPathAssertions { - JsonPathAssertions(WebTestClient.BodyContentSpec spec, String content, String expression, + + JsonPathAssertions( + WebTestClient.BodyContentSpec spec, String content, String expression, @Nullable Configuration configuration) { + super(spec, content, expression, configuration); } } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/StatusAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/StatusAssertions.java index 0c0d87e82fa..91fa59ee527 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/StatusAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/StatusAssertions.java @@ -29,10 +29,12 @@ import org.springframework.test.web.support.AbstractStatusAssertions; */ public class StatusAssertions extends AbstractStatusAssertions { + StatusAssertions(ExchangeResult result, WebTestClient.ResponseSpec spec) { super(result, spec); } + @Override protected void assertWithDiagnostics(Runnable assertion) { exchangeResult.assertWithDiagnostics(assertion); diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java index 4d9c30fbc1b..2ffaa16168f 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java @@ -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. * *

Use one of the bindToXxx methods to create an instance. For example: *

    @@ -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 { /** - * 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 { } /** - * 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 { } /** - * 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. *

     	 * WebTestClient client = WebTestClient.bindToServer()
     	 *         .baseUrl("http://localhost:8080")
    @@ -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 {
     		Builder defaultHeaders(Consumer 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 {
     	 * Specification for providing body of a request.
     	 */
     	interface RequestBodySpec extends RequestHeadersSpec {
    +
     		/**
     		 * Set the length of the body in bytes, as specified by the
     		 * {@code Content-Length} header.
    @@ -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 {
     
     		/**
     		 * 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 {
     
     		/**
     		 * 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 {
     		/**
     		 * 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 {
     		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}.
     		 * 

    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 diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/CookieAssertions.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/CookieAssertions.java index b4f8cced077..e5a033f8252 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/CookieAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/CookieAssertions.java @@ -28,10 +28,12 @@ import org.springframework.util.MultiValueMap; */ public class CookieAssertions extends AbstractCookieAssertions { - 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); diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClient.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClient.java index d831a68af03..4e69ffdc134 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClient.java @@ -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 { 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 { } @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 { @Override public > Builder mutate() { - return new DefaultRestTestClientBuilder<>(this.restClientBuilder); + return new DefaultRestTestClientBuilder<>(this.restClient.mutate()); } @@ -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 uriVariables) { - this.requestBodySpec = this.requestHeadersUriSpec.uri(uri, uriVariables); + this.requestHeadersUriSpec.uri(uri, uriVariables); return this; } @Override public RequestBodySpec uri(Function 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 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> 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> attributesConsumer) { - this.requestBodySpec = this.requestHeadersUriSpec.attributes(attributesConsumer); + this.requestHeadersUriSpec.attributes(attributesConsumer); return this; } @@ -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 { 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 { @Override public BodySpec expectBody(Class bodyType) { B body = this.exchangeResult.getBody(bodyType); - return new DefaultBodySpec<>(new EntityExchangeResult<>(this.exchangeResult, body)); + EntityExchangeResult result = new EntityExchangeResult<>(this.exchangeResult, body); + return new DefaultBodySpec<>(result); } @Override public BodySpec expectBody(ParameterizedTypeReference bodyType) { B body = this.exchangeResult.getBody(bodyType); - return new DefaultBodySpec<>(new EntityExchangeResult<>(this.exchangeResult, body)); + EntityExchangeResult 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 result = new EntityExchangeResult<>(this.exchangeResult, body); + return new DefaultBodyContentSpec(result); } @Override @@ -318,20 +317,26 @@ class DefaultRestTestClient implements RestTestClient { private final EntityExchangeResult result; - DefaultBodySpec(@Nullable EntityExchangeResult result) { - this.result = Objects.requireNonNull(result, "exchangeResult must be non-null"); + DefaultBodySpec(EntityExchangeResult result) { + this.result = result; } @Override - public T isEqualTo(B expected) { + public T isEqualTo(@Nullable B expected) { this.result.assertWithDiagnostics(() -> AssertionErrors.assertEquals("Response body", expected, this.result.getResponseBody())); return self(); } + @Override + public T value(Matcher matcher) { + this.result.assertWithDiagnostics(() -> MatcherAssert.assertThat(this.result.getResponseBody(), matcher)); + return self(); + } + @Override @SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1129 - public T value(Function bodyMapper, Matcher matcher) { + public T value(Function<@Nullable B, @Nullable R> bodyMapper, Matcher matcher) { this.result.assertWithDiagnostics(() -> { B body = this.result.getResponseBody(); MatcherAssert.assertThat(bodyMapper.apply(body), matcher); @@ -340,7 +345,8 @@ class DefaultRestTestClient implements RestTestClient { } @Override - public T value(Consumer consumer) { + @SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1129 + public T value(Consumer<@Nullable B> consumer) { this.result.assertWithDiagnostics(() -> consumer.accept(this.result.getResponseBody())); return self(); } @@ -374,8 +380,7 @@ class DefaultRestTestClient implements RestTestClient { @Override public EntityExchangeResult 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); } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClientBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClientBuilder.java index 48c1d255b07..748deb67aaf 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClientBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClientBuilder.java @@ -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; * * @author Rob Worsnop * @author Rossen Stoyanchev - * @param the type of the builder * @since 7.0 + * @param the type of the builder */ class DefaultRestTestClientBuilder> implements RestTestClient.Builder { @@ -92,12 +94,6 @@ class DefaultRestTestClientBuilder> implemen return self(); } - @Override - public T apply(Consumer> builderConsumer) { - builderConsumer.accept(this); - return self(); - } - @SuppressWarnings("unchecked") protected T self() { return (T) this; @@ -113,8 +109,13 @@ class DefaultRestTestClientBuilder> implemen } + /** + * Base class for implementations for {@link MockMvcSetupBuilder}. + * @param the "self" type of the builder + * @param the type of {@link MockMvc} builder + */ static class AbstractMockMvcSetupBuilder, M extends MockMvcBuilder> - extends DefaultRestTestClientBuilder implements RestTestClient.MockMvcSetupBuilder { + extends DefaultRestTestClientBuilder implements MockMvcSetupBuilder { private final M mockMvcBuilder; @@ -136,8 +137,12 @@ class DefaultRestTestClientBuilder> implemen } - static class DefaultStandaloneSetupBuilder extends AbstractMockMvcSetupBuilder - implements RestTestClient.StandaloneSetupBuilder { + /** + * Default implementation of {@link StandaloneSetupBuilder}. + */ + static class DefaultStandaloneSetupBuilder + extends AbstractMockMvcSetupBuilder + implements StandaloneSetupBuilder { DefaultStandaloneSetupBuilder(Object... controllers) { super(MockMvcBuilders.standaloneSetup(controllers)); @@ -145,8 +150,12 @@ class DefaultRestTestClientBuilder> implemen } - static class DefaultRouterFunctionSetupBuilder extends AbstractMockMvcSetupBuilder - implements RestTestClient.RouterFunctionSetupBuilder { + /** + * Default implementation of {@link RouterFunctionSetupBuilder}. + */ + static class DefaultRouterFunctionSetupBuilder + extends AbstractMockMvcSetupBuilder + implements RouterFunctionSetupBuilder { DefaultRouterFunctionSetupBuilder(RouterFunction... routerFunctions) { super(MockMvcBuilders.routerFunctions(routerFunctions)); @@ -155,8 +164,12 @@ class DefaultRestTestClientBuilder> implemen } - static class DefaultWebAppContextSetupBuilder extends AbstractMockMvcSetupBuilder - implements RestTestClient.WebAppContextSetupBuilder { + /** + * Default implementation of {@link WebAppContextSetupBuilder}. + */ + static class DefaultWebAppContextSetupBuilder + extends AbstractMockMvcSetupBuilder + implements WebAppContextSetupBuilder { DefaultWebAppContextSetupBuilder(WebApplicationContext context) { super(MockMvcBuilders.webAppContextSetup(context)); diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/ExchangeResult.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/ExchangeResult.java index bb899d2a8b0..a440f016d45 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/ExchangeResult.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/ExchangeResult.java @@ -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; 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 * {@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 { 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 { } } + /** + * Return the response headers received from the server. + */ public HttpHeaders getResponseHeaders() { return this.clientResponse.getHeaders(); } + /** + * Return response cookies received from the server. + */ + public MultiValueMap 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 getBody(Class bodyType) { return this.clientResponse.bodyTo(bodyType); @@ -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 { } } - /** - * Return response cookies received from the server. - */ - public MultiValueMap 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(); - } } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/HeaderAssertions.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/HeaderAssertions.java index 89e557b93e5..777097e67d6 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/HeaderAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/HeaderAssertions.java @@ -29,7 +29,7 @@ import org.springframework.test.web.support.AbstractHeaderAssertions; public class HeaderAssertions extends AbstractHeaderAssertions { - public HeaderAssertions(ExchangeResult exchangeResult, RestTestClient.ResponseSpec responseSpec) { + HeaderAssertions(ExchangeResult exchangeResult, RestTestClient.ResponseSpec responseSpec) { super(exchangeResult, responseSpec); } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/JsonPathAssertions.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/JsonPathAssertions.java index b5eda5ba59a..efe57d0b3d2 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/JsonPathAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/JsonPathAssertions.java @@ -26,13 +26,19 @@ import org.springframework.test.web.support.AbstractJsonPathAssertions; * JsonPath assertions. * * @author Rob Worsnop + * @author Rossen Stoyanchev * @since 7.0 * @see https://github.com/jayway/JsonPath * @see JsonPathExpectationsHelper */ public class JsonPathAssertions extends AbstractJsonPathAssertions { - 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); } + } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/RestTestClient.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/RestTestClient.java index 32bbed7535a..2394060227a 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/RestTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/RestTestClient.java @@ -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; 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. + * + *

    Use one of the bindToXxx methods to create an instance. For example: + *

      + *
    • {@link #bindToController(Object...)} + *
    • {@link #bindToRouterFunction(RouterFunction[])} + *
    • {@link #bindToApplicationContext(WebApplicationContext)} + *
    • {@link #bindToServer()} + *
    • ... + *
    * * @author Rob Worsnop * @author Rossen Stoyanchev @@ -121,34 +134,24 @@ public interface RestTestClient { /** - * Begin creating a {@link RestTestClient} by providing the {@code @Controller} - * instance(s) to handle requests with. - *

    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. - *

    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. - *

    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 { } /** - * 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. *

     	 * RestTestClient client = RestTestClient.bindToServer()
     	 *         .baseUrl("http://localhost:8080")
    @@ -186,12 +188,14 @@ public interface RestTestClient {
     	}
     
     
    +	/**
    +	 * Steps to customize the underlying {@link RestClient} via {@link RestClient.Builder}.
    +	 * @param  the type of builder
    +	 */
     	interface Builder> {
     
     		/**
    -		 * 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 baseUrl(String baseUrl);
     
    @@ -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 defaultHeader(String headerName, String... headerValues);
    @@ -220,8 +224,8 @@ public interface RestTestClient {
     		 T defaultHeaders(Consumer 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 defaultCookie(String cookieName, String... cookieValues);
    @@ -237,39 +241,48 @@ public interface RestTestClient {
     		 */
     		 T defaultCookies(Consumer> cookiesConsumer);
     
    -		/**
    -		 * Apply the given {@code Consumer} to this builder instance.
    -		 * 

    This can be useful for applying pre-packaged customizations. - * @param builderConsumer the consumer to apply - */ - T apply(Consumer> builderConsumer); - /** * Build the {@link RestTestClient} instance. */ RestTestClient build(); - } + } - interface MockMvcSetupBuilder, M extends MockMvcBuilder> extends Builder { + /** + * Extension of {@link Builder} for tests against a MockMvc server. + * @param the builder type + * @param the type of {@link MockMvc} setup + */ + interface MockMvcSetupBuilder, M extends MockMvcBuilder> extends Builder { - T configureServer(Consumer consumer); + T configureServer(Consumer consumer); } + /** + * Extension of {@link Builder} for tests витх а + * {@link MockMvcBuilders#standaloneSetup(Object...) standalone MockMvc setup}. + */ interface StandaloneSetupBuilder extends MockMvcSetupBuilder { } + /** + * Extension of {@link Builder} for tests витх а + * {@link MockMvcBuilders#routerFunctions(RouterFunction[]) RouterFunction MockMvc setup}. + */ interface RouterFunctionSetupBuilder extends MockMvcSetupBuilder { } + /** + * Extension of {@link Builder} for tests витх а + * {@link MockMvcBuilders#webAppContextSetup(WebApplicationContext) WebAppContext MockMvc setup}. + */ interface WebAppContextSetupBuilder extends MockMvcSetupBuilder { } - /** * Specification for providing the URI of a request. * @@ -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 { * 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 uriVariables); + S uri(String uri, Map uriVariables); /** * Build the URI for the request with a {@link UriBuilder} obtained @@ -419,6 +432,16 @@ public interface RestTestClient { * Specification for providing body of a request. */ interface RequestBodySpec extends RequestHeadersSpec { + + /** + * 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 { /** * 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 { 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. *

    If a single {@link Error} or {@link RuntimeException} is thrown, @@ -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. */ EntityExchangeResult returnResult(Class elementClass); @@ -534,8 +556,8 @@ public interface RestTestClient { EntityExchangeResult returnResult(ParameterizedTypeReference 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 { @@ -554,18 +576,24 @@ public interface RestTestClient { /** * Assert the extracted body is equal to the given value. */ - T isEqualTo(B expected); + T isEqualTo(@Nullable B expected); + + /** + * Assert the extracted body with a {@link Matcher}. + * @since 5.1 + */ + T value(Matcher matcher); /** * Transform the extracted the body with a function, for example, extracting a * property, and assert the mapped value with a {@link Matcher}. */ - T value(Function bodyMapper, Matcher matcher); + T value(Function<@Nullable B, @Nullable R> bodyMapper, Matcher matcher); /** * Assert the extracted body with a {@link Consumer}. */ - T value(Consumer consumer); + T value(Consumer<@Nullable B> consumer); /** * Assert the exchange result with the given {@link Consumer}. diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/StatusAssertions.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/StatusAssertions.java index c2f76ef22b0..f29df0975ab 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/StatusAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/StatusAssertions.java @@ -29,10 +29,12 @@ import org.springframework.test.web.support.AbstractStatusAssertions; */ public class StatusAssertions extends AbstractStatusAssertions { - public StatusAssertions(ExchangeResult exchangeResult, ResponseSpec responseSpec) { + + StatusAssertions(ExchangeResult exchangeResult, ResponseSpec responseSpec) { super(exchangeResult, responseSpec); } + @Override protected void assertWithDiagnostics(Runnable assertion) { exchangeResult.assertWithDiagnostics(assertion); diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/XpathAssertions.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/XpathAssertions.java index cfec64a17ee..253bb0fbc63 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/XpathAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/XpathAssertions.java @@ -33,11 +33,15 @@ import org.springframework.util.Assert; */ public class XpathAssertions extends AbstractXpathAssertions { - XpathAssertions(RestTestClient.BodyContentSpec spec, - String expression, @Nullable Map namespaces, Object... args) { + + XpathAssertions( + RestTestClient.BodyContentSpec spec, + String expression, @Nullable Map namespaces, Object... args) { + super(spec, expression, namespaces, args); } + @Override protected Optional getResponseHeaders() { return Optional.of(bodySpec.returnResult()) diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/client/samples/RestTestClientTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/client/samples/RestTestClientTests.java index 381c520ca7f..af1be6b11bc 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/client/samples/RestTestClientTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/client/samples/RestTestClientTests.java @@ -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"))