Browse Source

Add interceptors and converters to RestTestClient.Builder

Closes gh-35268
pull/34146/merge
rstoyanchev 6 months ago
parent
commit
96bc1f50c7
  1. 5
      spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java
  2. 56
      spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java
  3. 36
      spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClient.java
  4. 41
      spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClientBuilder.java
  5. 51
      spring-test/src/main/java/org/springframework/test/web/servlet/client/RestTestClient.java

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

@ -115,11 +115,6 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { @@ -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) {

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

@ -242,7 +242,7 @@ public interface WebTestClient { @@ -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 { @@ -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 -&gt; ... ));
*
* client.get().uri("/accounts")
* .exchange()
* .expectBodyList(Person.class).consumeWith(exchangeResult -&gt; ... ));
*
* client.get().uri("/accounts/1")
* .exchange()
* .expectBody().consumeWith(exchangeResult -&gt; ... ));
* </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 { @@ -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 -&gt; ... ));
*
* client.get().uri("/accounts")
* .exchange()
* .expectBodyList(Person.class).consumeWith(exchangeResult -&gt; ... ));
*
* client.get().uri("/accounts/1")
* .exchange()
* .expectBody().consumeWith(exchangeResult -&gt; ... ));
* </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.

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

@ -56,11 +56,20 @@ class DefaultRestTestClient implements RestTestClient { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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

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

@ -16,10 +16,13 @@ @@ -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; @@ -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 @@ -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 @@ -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 @@ -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
spring-test/src/main/java/org/springframework/test/web/servlet/client/RestTestClient.java

@ -19,6 +19,7 @@ package org.springframework.test.web.servlet.client; @@ -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; @@ -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 { @@ -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 -&gt; ... ));
*
* client.get().uri("/accounts")
* .exchange()
* .expectBodyList(Person.class).consumeWith(exchangeResult -&gt; ... ));
*
* client.get().uri("/accounts/1")
* .exchange()
* .expectBody().consumeWith(exchangeResult -&gt; ... ));
* </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.
*/

Loading…
Cancel
Save