mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Add interceptors and converters to RestTestClient.Builder
Closes gh-35268
This commit is contained in:
-5
@@ -115,11 +115,6 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
|
||||
this(httpHandlerBuilder, null, sslInfo);
|
||||
}
|
||||
|
||||
/** Use given connector. */
|
||||
DefaultWebTestClientBuilder(ClientHttpConnector connector) {
|
||||
this(null, connector, null);
|
||||
}
|
||||
|
||||
private DefaultWebTestClientBuilder(@Nullable WebHttpHandlerBuilder httpHandlerBuilder,
|
||||
@Nullable ClientHttpConnector connector, @Nullable SslInfo sslInfo) {
|
||||
|
||||
|
||||
+28
-28
@@ -242,7 +242,7 @@ public interface WebTestClient {
|
||||
* @since 5.0.2
|
||||
*/
|
||||
static Builder bindToServer(ClientHttpConnector connector) {
|
||||
return new DefaultWebTestClientBuilder(connector);
|
||||
return new DefaultWebTestClientBuilder().clientConnector(connector);
|
||||
}
|
||||
|
||||
|
||||
@@ -467,33 +467,6 @@ public interface WebTestClient {
|
||||
*/
|
||||
Builder filters(Consumer<List<ExchangeFilterFunction>> filtersConsumer);
|
||||
|
||||
/**
|
||||
* Configure an {@code EntityExchangeResult} callback that is invoked
|
||||
* every time after a response is fully decoded to a single entity, to a
|
||||
* List of entities, or to a byte[]. In effect, equivalent to each and
|
||||
* all of the below but registered once, globally:
|
||||
* <pre>
|
||||
* client.get().uri("/accounts/1")
|
||||
* .exchange()
|
||||
* .expectBody(Person.class).consumeWith(exchangeResult -> ... ));
|
||||
*
|
||||
* client.get().uri("/accounts")
|
||||
* .exchange()
|
||||
* .expectBodyList(Person.class).consumeWith(exchangeResult -> ... ));
|
||||
*
|
||||
* client.get().uri("/accounts/1")
|
||||
* .exchange()
|
||||
* .expectBody().consumeWith(exchangeResult -> ... ));
|
||||
* </pre>
|
||||
* <p>Note that the configured consumer does not apply to responses
|
||||
* decoded to {@code Flux<T>} which can be consumed outside the workflow
|
||||
* of the test client, for example via {@code reactor.test.StepVerifier}.
|
||||
* @param consumer the consumer to apply to entity responses
|
||||
* @return the builder
|
||||
* @since 5.3.5
|
||||
*/
|
||||
Builder entityExchangeResultConsumer(Consumer<EntityExchangeResult<?>> consumer);
|
||||
|
||||
/**
|
||||
* Configure the codecs for the {@code WebClient} in the
|
||||
* {@link #exchangeStrategies(ExchangeStrategies) underlying}
|
||||
@@ -533,6 +506,33 @@ public interface WebTestClient {
|
||||
*/
|
||||
Builder clientConnector(ClientHttpConnector connector);
|
||||
|
||||
/**
|
||||
* Configure an {@code EntityExchangeResult} callback that is invoked
|
||||
* every time after a response is fully decoded to a single entity, to a
|
||||
* List of entities, or to a byte[]. In effect, equivalent to each and
|
||||
* all of the below but registered once, globally:
|
||||
* <pre>
|
||||
* client.get().uri("/accounts/1")
|
||||
* .exchange()
|
||||
* .expectBody(Person.class).consumeWith(exchangeResult -> ... ));
|
||||
*
|
||||
* client.get().uri("/accounts")
|
||||
* .exchange()
|
||||
* .expectBodyList(Person.class).consumeWith(exchangeResult -> ... ));
|
||||
*
|
||||
* client.get().uri("/accounts/1")
|
||||
* .exchange()
|
||||
* .expectBody().consumeWith(exchangeResult -> ... ));
|
||||
* </pre>
|
||||
* <p>Note that the configured consumer does not apply to responses
|
||||
* decoded to {@code Flux<T>} which can be consumed outside the workflow
|
||||
* of the test client, for example via {@code reactor.test.StepVerifier}.
|
||||
* @param consumer the consumer to apply to entity responses
|
||||
* @return the builder
|
||||
* @since 5.3.5
|
||||
*/
|
||||
Builder entityExchangeResultConsumer(Consumer<EntityExchangeResult<?>> consumer);
|
||||
|
||||
/**
|
||||
* Apply the given configurer to this builder instance.
|
||||
* <p>This can be useful for applying pre-packaged customizations.
|
||||
|
||||
+28
-8
@@ -56,11 +56,20 @@ class DefaultRestTestClient implements RestTestClient {
|
||||
|
||||
private final RestClient restClient;
|
||||
|
||||
private final Consumer<EntityExchangeResult<?>> entityResultConsumer;
|
||||
|
||||
private final DefaultRestTestClientBuilder<?> restTestClientBuilder;
|
||||
|
||||
private final AtomicLong requestIndex = new AtomicLong();
|
||||
|
||||
|
||||
DefaultRestTestClient(RestClient.Builder builder) {
|
||||
DefaultRestTestClient(
|
||||
RestClient.Builder builder, Consumer<EntityExchangeResult<?>> entityResultConsumer,
|
||||
DefaultRestTestClientBuilder<?> restTestClientBuilder) {
|
||||
|
||||
this.restClient = builder.build();
|
||||
this.entityResultConsumer = entityResultConsumer;
|
||||
this.restTestClientBuilder = restTestClientBuilder;
|
||||
}
|
||||
|
||||
|
||||
@@ -108,9 +117,10 @@ class DefaultRestTestClient implements RestTestClient {
|
||||
return new DefaultRequestBodyUriSpec(this.restClient.method(httpMethod));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <B extends Builder<B>> Builder<B> mutate() {
|
||||
return new DefaultRestTestClientBuilder<>(this.restClient.mutate());
|
||||
return (Builder<B>) this.restTestClientBuilder;
|
||||
}
|
||||
|
||||
|
||||
@@ -242,7 +252,8 @@ class DefaultRestTestClient implements RestTestClient {
|
||||
public ResponseSpec exchange() {
|
||||
return new DefaultResponseSpec(
|
||||
this.requestHeadersUriSpec.exchangeForRequiredValue(
|
||||
(request, response) -> new ExchangeResult(request, response, this.uriTemplate), false));
|
||||
(request, response) -> new ExchangeResult(request, response, this.uriTemplate), false),
|
||||
DefaultRestTestClient.this.entityResultConsumer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,8 +262,11 @@ class DefaultRestTestClient implements RestTestClient {
|
||||
|
||||
private final ExchangeResult exchangeResult;
|
||||
|
||||
DefaultResponseSpec(ExchangeResult result) {
|
||||
private final Consumer<EntityExchangeResult<?>> entityResultConsumer;
|
||||
|
||||
DefaultResponseSpec(ExchangeResult result, Consumer<EntityExchangeResult<?>> entityResultConsumer) {
|
||||
this.exchangeResult = result;
|
||||
this.entityResultConsumer = entityResultConsumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -280,25 +294,31 @@ class DefaultRestTestClient implements RestTestClient {
|
||||
@Override
|
||||
public <B> BodySpec<B, ?> expectBody(ParameterizedTypeReference<B> bodyType) {
|
||||
B body = this.exchangeResult.getBody(bodyType);
|
||||
EntityExchangeResult<B> result = new EntityExchangeResult<>(this.exchangeResult, body);
|
||||
EntityExchangeResult<B> result = initExchangeResult(body);
|
||||
return new DefaultBodySpec<>(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BodyContentSpec expectBody() {
|
||||
byte[] body = this.exchangeResult.getBody(byte[].class);
|
||||
EntityExchangeResult<byte[]> result = new EntityExchangeResult<>(this.exchangeResult, body);
|
||||
EntityExchangeResult<byte[]> result = initExchangeResult(body);
|
||||
return new DefaultBodyContentSpec(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> EntityExchangeResult<T> returnResult(Class<T> elementClass) {
|
||||
return new EntityExchangeResult<>(this.exchangeResult, this.exchangeResult.getBody(elementClass));
|
||||
return initExchangeResult(this.exchangeResult.getBody(elementClass));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> EntityExchangeResult<T> returnResult(ParameterizedTypeReference<T> elementTypeRef) {
|
||||
return new EntityExchangeResult<>(this.exchangeResult, this.exchangeResult.getBody(elementTypeRef));
|
||||
return initExchangeResult(this.exchangeResult.getBody(elementTypeRef));
|
||||
}
|
||||
|
||||
private <B> EntityExchangeResult<B> initExchangeResult(@Nullable B body) {
|
||||
EntityExchangeResult<B> result = new EntityExchangeResult<>(this.exchangeResult, body);
|
||||
result.assertWithDiagnostics(() -> this.entityResultConsumer.accept(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+39
-2
@@ -16,10 +16,13 @@
|
||||
|
||||
package org.springframework.test.web.servlet.client;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.converter.HttpMessageConverters;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MockMvcBuilder;
|
||||
import org.springframework.test.web.servlet.client.RestTestClient.MockMvcSetupBuilder;
|
||||
@@ -30,6 +33,7 @@ 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.Assert;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.ApiVersionInserter;
|
||||
import org.springframework.web.client.RestClient;
|
||||
@@ -49,15 +53,22 @@ class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implemen
|
||||
|
||||
private final RestClient.Builder restClientBuilder;
|
||||
|
||||
private Consumer<EntityExchangeResult<?>> entityResultConsumer = result -> {};
|
||||
|
||||
|
||||
DefaultRestTestClientBuilder() {
|
||||
this.restClientBuilder = RestClient.builder();
|
||||
this(RestClient.builder());
|
||||
}
|
||||
|
||||
DefaultRestTestClientBuilder(RestClient.Builder restClientBuilder) {
|
||||
this.restClientBuilder = restClientBuilder;
|
||||
}
|
||||
|
||||
DefaultRestTestClientBuilder(DefaultRestTestClientBuilder<B> other) {
|
||||
this.restClientBuilder = other.restClientBuilder.clone();
|
||||
this.entityResultConsumer = other.entityResultConsumer;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T extends B> T baseUrl(String baseUrl) {
|
||||
@@ -107,6 +118,31 @@ class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implemen
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends B> T requestInterceptor(ClientHttpRequestInterceptor interceptor) {
|
||||
this.restClientBuilder.requestInterceptor(interceptor);
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends B> T requestInterceptors(Consumer<List<ClientHttpRequestInterceptor>> interceptorsConsumer) {
|
||||
this.restClientBuilder.requestInterceptors(interceptorsConsumer);
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends B> T configureMessageConverters(Consumer<HttpMessageConverters.ClientBuilder> configurer) {
|
||||
this.restClientBuilder.configureMessageConverters(configurer);
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends B> T entityExchangeResultConsumer(Consumer<EntityExchangeResult<?>> entityResultConsumer) {
|
||||
Assert.notNull(entityResultConsumer, "'entityResultConsumer' is required");
|
||||
this.entityResultConsumer = this.entityResultConsumer.andThen(entityResultConsumer);
|
||||
return self();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends B> T self() {
|
||||
return (T) this;
|
||||
@@ -118,7 +154,8 @@ class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implemen
|
||||
|
||||
@Override
|
||||
public RestTestClient build() {
|
||||
return new DefaultRestTestClient(this.restClientBuilder);
|
||||
return new DefaultRestTestClient(
|
||||
this.restClientBuilder, this.entityResultConsumer, new DefaultRestTestClientBuilder<>(this));
|
||||
}
|
||||
|
||||
|
||||
|
||||
+51
@@ -19,6 +19,7 @@ package org.springframework.test.web.servlet.client;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
@@ -31,6 +32,8 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.converter.HttpMessageConverters;
|
||||
import org.springframework.test.json.JsonComparator;
|
||||
import org.springframework.test.json.JsonCompareMode;
|
||||
import org.springframework.test.json.JsonComparison;
|
||||
@@ -261,6 +264,54 @@ public interface RestTestClient {
|
||||
*/
|
||||
<T extends B> T apiVersionInserter(ApiVersionInserter apiVersionInserter);
|
||||
|
||||
/**
|
||||
* Add the given request interceptor to the end of the interceptor chain.
|
||||
* @param interceptor the interceptor to be added to the chain
|
||||
*/
|
||||
<T extends B> T requestInterceptor(ClientHttpRequestInterceptor interceptor);
|
||||
|
||||
/**
|
||||
* Manipulate the interceptors with the given consumer. The list provided to
|
||||
* the consumer is "live", so that the consumer can be used to remove
|
||||
* interceptors, change ordering, etc.
|
||||
* @param interceptorsConsumer a function that consumes the interceptors list
|
||||
* @return this builder
|
||||
*/
|
||||
<T extends B> T requestInterceptors(Consumer<List<ClientHttpRequestInterceptor>> interceptorsConsumer);
|
||||
|
||||
/**
|
||||
* Configure the message converters to use for the request and response body.
|
||||
* @param configurer the configurer to apply on an empty {@link HttpMessageConverters.ClientBuilder}.
|
||||
* @return this builder
|
||||
*/
|
||||
<T extends B> T configureMessageConverters(Consumer<HttpMessageConverters.ClientBuilder> configurer);
|
||||
|
||||
/**
|
||||
* Configure an {@code EntityExchangeResult} callback that is invoked
|
||||
* every time after a response is fully decoded to a single entity, to a
|
||||
* List of entities, or to a byte[]. In effect, equivalent to each and
|
||||
* all of the below but registered once, globally:
|
||||
* <pre>
|
||||
* client.get().uri("/accounts/1")
|
||||
* .exchange()
|
||||
* .expectBody(Person.class).consumeWith(exchangeResult -> ... ));
|
||||
*
|
||||
* client.get().uri("/accounts")
|
||||
* .exchange()
|
||||
* .expectBodyList(Person.class).consumeWith(exchangeResult -> ... ));
|
||||
*
|
||||
* client.get().uri("/accounts/1")
|
||||
* .exchange()
|
||||
* .expectBody().consumeWith(exchangeResult -> ... ));
|
||||
* </pre>
|
||||
* <p>Note that the configured consumer does not apply to responses
|
||||
* decoded to {@code Flux<T>} which can be consumed outside the workflow
|
||||
* of the test client, for example via {@code reactor.test.StepVerifier}.
|
||||
* @param consumer the consumer to apply to entity responses
|
||||
* @return the builder
|
||||
*/
|
||||
<T extends B> T entityExchangeResultConsumer(Consumer<EntityExchangeResult<?>> consumer);
|
||||
|
||||
/**
|
||||
* Build the {@link RestTestClient} instance.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user