From 3f2519b617756f36be0cec212857600d43a47107 Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Thu, 30 Mar 2023 11:33:57 +0800 Subject: [PATCH 1/2] Enhance MockMvcWebTestClient to allow applying RequestPostProcessors --- .../reactive/server/DefaultWebTestClient.java | 4 +- .../server/DefaultWebTestClientBuilder.java | 38 ++-- .../client/AbstractMockMvcServerSpec.java | 9 +- .../client/DefaultMockMvcWebTestClient.java | 43 ++++ .../DefaultMockMvcWebTestClientBuilder.java | 200 ++++++++++++++++++ .../servlet/client/MockMvcHttpConnector.java | 14 +- .../servlet/client/MockMvcWebTestClient.java | 141 +++++++++++- .../MockMvcWebTestClientConfigurer.java | 45 ++++ 8 files changed, 455 insertions(+), 39 deletions(-) create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultMockMvcWebTestClient.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultMockMvcWebTestClientBuilder.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClientConfigurer.java 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 664640874844..76c6edecdf37 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 @@ -68,7 +68,7 @@ * @author MichaƂ Rowicki * @since 5.0 */ -class DefaultWebTestClient implements WebTestClient { +public class DefaultWebTestClient implements WebTestClient { private final WiretapConnector wiretapConnector; @@ -91,7 +91,7 @@ class DefaultWebTestClient implements WebTestClient { private final AtomicLong requestIndex = new AtomicLong(); - DefaultWebTestClient(ClientHttpConnector connector, + protected DefaultWebTestClient(ClientHttpConnector connector, Function exchangeFactory, UriBuilderFactory uriBuilderFactory, @Nullable HttpHeaders headers, @Nullable MultiValueMap cookies, Consumer> entityResultConsumer, diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java index f3c9e3650af0..fe3ab134f4e2 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java @@ -51,7 +51,7 @@ * @author Rossen Stoyanchev * @since 5.0 */ -class DefaultWebTestClientBuilder implements WebTestClient.Builder { +public class DefaultWebTestClientBuilder implements WebTestClient.Builder { private static final boolean reactorNettyClientPresent; @@ -77,54 +77,54 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { @Nullable - private final WebHttpHandlerBuilder httpHandlerBuilder; + protected final WebHttpHandlerBuilder httpHandlerBuilder; @Nullable - private final ClientHttpConnector connector; + protected final ClientHttpConnector connector; @Nullable - private String baseUrl; + protected String baseUrl; @Nullable - private UriBuilderFactory uriBuilderFactory; + protected UriBuilderFactory uriBuilderFactory; @Nullable - private HttpHeaders defaultHeaders; + protected HttpHeaders defaultHeaders; @Nullable - private MultiValueMap defaultCookies; + protected MultiValueMap defaultCookies; @Nullable - private List filters; + protected List filters; - private Consumer> entityResultConsumer = result -> {}; + protected Consumer> entityResultConsumer = result -> {}; @Nullable - private ExchangeStrategies strategies; + protected ExchangeStrategies strategies; @Nullable - private List> strategiesConfigurers; + protected List> strategiesConfigurers; @Nullable - private Duration responseTimeout; + protected Duration responseTimeout; /** Determine connector via classpath detection. */ - DefaultWebTestClientBuilder() { + protected DefaultWebTestClientBuilder() { this(null, null); } /** Use HttpHandlerConnector with mock server. */ - DefaultWebTestClientBuilder(WebHttpHandlerBuilder httpHandlerBuilder) { + protected DefaultWebTestClientBuilder(WebHttpHandlerBuilder httpHandlerBuilder) { this(httpHandlerBuilder, null); } /** Use given connector. */ - DefaultWebTestClientBuilder(ClientHttpConnector connector) { + protected DefaultWebTestClientBuilder(ClientHttpConnector connector) { this(null, connector); } - DefaultWebTestClientBuilder( + protected DefaultWebTestClientBuilder( @Nullable WebHttpHandlerBuilder httpHandlerBuilder, @Nullable ClientHttpConnector connector) { Assert.isTrue(httpHandlerBuilder == null || connector == null, @@ -139,7 +139,7 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { } /** Copy constructor. */ - DefaultWebTestClientBuilder(DefaultWebTestClientBuilder other) { + protected DefaultWebTestClientBuilder(DefaultWebTestClientBuilder other) { this.httpHandlerBuilder = (other.httpHandlerBuilder != null ? other.httpHandlerBuilder.clone() : null); this.connector = other.connector; this.responseTimeout = other.responseTimeout; @@ -323,7 +323,7 @@ else if (httpComponentsClientPresent) { } } - private ExchangeStrategies initExchangeStrategies() { + protected ExchangeStrategies initExchangeStrategies() { if (CollectionUtils.isEmpty(this.strategiesConfigurers)) { return (this.strategies != null ? this.strategies : ExchangeStrategies.withDefaults()); } @@ -333,7 +333,7 @@ private ExchangeStrategies initExchangeStrategies() { return builder.build(); } - private UriBuilderFactory initUriBuilderFactory() { + protected UriBuilderFactory initUriBuilderFactory() { if (this.uriBuilderFactory != null) { return this.uriBuilderFactory; } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/AbstractMockMvcServerSpec.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/AbstractMockMvcServerSpec.java index 02c24c01decb..d80334dee489 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/AbstractMockMvcServerSpec.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/AbstractMockMvcServerSpec.java @@ -18,8 +18,6 @@ import jakarta.servlet.Filter; -import org.springframework.http.client.reactive.ClientHttpConnector; -import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.servlet.DispatcherServletCustomizer; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.RequestBuilder; @@ -93,14 +91,13 @@ private T self() { protected abstract ConfigurableMockMvcBuilder getMockMvcBuilder(); @Override - public WebTestClient.Builder configureClient() { + public MockMvcWebTestClient.Builder configureClient() { MockMvc mockMvc = getMockMvcBuilder().build(); - ClientHttpConnector connector = new MockMvcHttpConnector(mockMvc); - return WebTestClient.bindToServer(connector); + return MockMvcWebTestClient.bindToMockMvc(mockMvc); } @Override - public WebTestClient build() { + public MockMvcWebTestClient build() { return configureClient().build(); } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultMockMvcWebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultMockMvcWebTestClient.java new file mode 100644 index 000000000000..b49c18f48901 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultMockMvcWebTestClient.java @@ -0,0 +1,43 @@ +package org.springframework.test.web.servlet.client; + +import java.time.Duration; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.test.web.reactive.server.DefaultWebTestClient; +import org.springframework.test.web.reactive.server.EntityExchangeResult; +import org.springframework.test.web.servlet.request.RequestPostProcessor; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.client.ExchangeFunction; +import org.springframework.web.util.UriBuilderFactory; + +public class DefaultMockMvcWebTestClient extends DefaultWebTestClient implements MockMvcWebTestClient { + private final DefaultMockMvcWebTestClientBuilder builder; + + DefaultMockMvcWebTestClient(ClientHttpConnector connector, + Function exchangeFactory, UriBuilderFactory uriBuilderFactory, + HttpHeaders headers, MultiValueMap cookies, + Consumer> entityResultConsumer, Duration responseTimeout, + DefaultMockMvcWebTestClientBuilder clientBuilder) { + super(connector, exchangeFactory, uriBuilderFactory, headers, cookies, entityResultConsumer, responseTimeout, + clientBuilder); + this.builder = clientBuilder; + } + + @Override + public MockMvcWebTestClient.Builder mutate() { + return new DefaultMockMvcWebTestClientBuilder(builder); + } + + @Override + public MockMvcWebTestClient mutateWith(RequestPostProcessor requestPostProcessor) { + return mutate().requestPostProcessor(requestPostProcessor).build(); + } + + @Override + public MockMvcWebTestClient mutateWith(MockMvcWebTestClientConfigurer configurer) { + return mutate().apply(configurer).build(); + } +} diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultMockMvcWebTestClientBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultMockMvcWebTestClientBuilder.java new file mode 100644 index 000000000000..b647e52b3984 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultMockMvcWebTestClientBuilder.java @@ -0,0 +1,200 @@ +package org.springframework.test.web.servlet.client; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.http.codec.ClientCodecConfigurer; +import org.springframework.test.web.reactive.server.DefaultWebTestClientBuilder; +import org.springframework.test.web.reactive.server.EntityExchangeResult; +import org.springframework.test.web.reactive.server.WebTestClientConfigurer; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.client.MockMvcWebTestClient.Builder; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.RequestPostProcessor; +import org.springframework.util.CollectionUtils; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.ExchangeFunction; +import org.springframework.web.reactive.function.client.ExchangeFunctions; +import org.springframework.web.reactive.function.client.ExchangeStrategies; +import org.springframework.web.util.UriBuilderFactory; + +public class DefaultMockMvcWebTestClientBuilder extends DefaultWebTestClientBuilder implements MockMvcWebTestClient.Builder { + + private MockMvc mockMvc; + private List requestPostProcessors = new ArrayList<>(); + private List> requestBuilderCustomizers = new ArrayList<>(); + + DefaultMockMvcWebTestClientBuilder(MockMvc mockMvc) { + this.mockMvc = mockMvc; + } + + /** Copy constructor. */ + DefaultMockMvcWebTestClientBuilder(DefaultMockMvcWebTestClientBuilder other) { + super(other); + this.mockMvc = other.mockMvc; + this.requestPostProcessors = new ArrayList<>(other.requestPostProcessors); + this.requestBuilderCustomizers = new ArrayList<>(other.requestBuilderCustomizers); + } + + @Override + public Builder mockMvc(MockMvc mockMvc) { + this.mockMvc = mockMvc; + return this; + } + + @Override + public Builder requestPostProcessors(Consumer> requestPostProcessorsConsumer) { + requestPostProcessorsConsumer.accept(requestPostProcessors); + return this; + } + + @Override + public Builder requestPostProcessor(RequestPostProcessor requestPostProcessor) { + requestPostProcessors.add(requestPostProcessor); + return this; + } + + @Override + public Builder requestBuilderCustomizer(Consumer requestBuilderCustomizer) { + this.requestBuilderCustomizers.add(requestBuilderCustomizer); + return this; + } + + @Override + public Builder requestBuilderCustomizers(Consumer>> requestBuilderCustomizersConsumer) { + requestBuilderCustomizersConsumer.accept(requestBuilderCustomizers); + return this; + } + + @Override + public Builder apply(MockMvcWebTestClientConfigurer configurer) { + configurer.afterConfigurerAdded(this, this.httpHandlerBuilder, this.connector); + return this; + } + + @Override + public MockMvcWebTestClient build() { + ClientHttpConnector connectorToUse = new MockMvcHttpConnector(mockMvc, customizer -> { + requestPostProcessors.forEach(customizer::with); + requestBuilderCustomizers.forEach(builderCustomizer -> builderCustomizer.accept(customizer)); + }); + Function exchangeFactory = connector -> { + ExchangeFunction exchange = ExchangeFunctions.create(connector, initExchangeStrategies()); + if (CollectionUtils.isEmpty(this.filters)) { + return exchange; + } + return this.filters.stream() + .reduce(ExchangeFilterFunction::andThen) + .map(filter -> filter.apply(exchange)) + .orElse(exchange); + + }; + return new DefaultMockMvcWebTestClient(connectorToUse, exchangeFactory, initUriBuilderFactory(), + this.defaultHeaders != null ? HttpHeaders.readOnlyHttpHeaders(this.defaultHeaders) : null, + this.defaultCookies != null ? CollectionUtils.unmodifiableMultiValueMap(this.defaultCookies) : null, + this.entityResultConsumer, this.responseTimeout, new DefaultMockMvcWebTestClientBuilder(this)); + } + + /* Override methods to return covariant return type */ + @Override + public Builder baseUrl(String baseUrl) { + super.baseUrl(baseUrl); + return this; + } + + @Override + public Builder uriBuilderFactory( + UriBuilderFactory uriBuilderFactory) { + super.uriBuilderFactory(uriBuilderFactory); + return this; + } + + @Override + public Builder defaultHeader(String header, + String... values) { + super.defaultHeader(header, values); + return this; + } + + @Override + public Builder defaultHeaders( + Consumer headersConsumer) { + super.defaultHeaders(headersConsumer); + return this; + } + + @Override + public Builder defaultCookie(String cookie, + String... values) { + super.defaultCookie(cookie, values); + return this; + } + + @Override + public Builder defaultCookies( + Consumer> cookiesConsumer) { + super.defaultCookies(cookiesConsumer); + return this; + } + + @Override + public Builder filter(ExchangeFilterFunction filter) { + super.filter(filter); + return this; + } + + @Override + public Builder filters( + Consumer> filtersConsumer) { + super.filters(filtersConsumer); + return this; + } + + @Override + public Builder entityExchangeResultConsumer( + Consumer> entityResultConsumer) { + super.entityExchangeResultConsumer(entityResultConsumer); + return this; + } + + @Override + public Builder codecs( + Consumer configurer) { + super.codecs(configurer); + return this; + } + + @Override + public Builder exchangeStrategies( + ExchangeStrategies strategies) { + super.exchangeStrategies(strategies); + return this; + } + + @Override + @SuppressWarnings("deprecation") + public Builder exchangeStrategies( + Consumer configurer) { + super.exchangeStrategies(configurer); + return this; + } + + @Override + public Builder apply( + WebTestClientConfigurer configurer) { + super.apply(configurer); + return this; + } + + @Override + public Builder responseTimeout(Duration timeout) { + super.responseTimeout(timeout); + return this; + } +} diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java index adcb0115cfde..11d223ab4d22 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import java.util.function.Function; import jakarta.servlet.http.Cookie; @@ -81,13 +82,14 @@ public class MockMvcHttpConnector implements ClientHttpConnector { private final MockMvc mockMvc; + + private final Consumer requestBuilderCustomizer; - - public MockMvcHttpConnector(MockMvc mockMvc) { + public MockMvcHttpConnector(MockMvc mockMvc, Consumer requestBuilderCustomizer) { this.mockMvc = mockMvc; + this.requestBuilderCustomizer = requestBuilderCustomizer; } - @Override public Mono connect( HttpMethod method, URI uri, Function> requestCallback) { @@ -147,6 +149,9 @@ private MockHttpServletRequestBuilder initRequestBuilder( if (!ObjectUtils.isEmpty(bytes)) { requestBuilder.content(bytes); } + if(requestBuilderCustomizer != null) { + requestBuilderCustomizer.accept(requestBuilder); + } return requestBuilder; } @@ -175,6 +180,9 @@ private MockHttpServletRequestBuilder initRequestBuilder( })) .blockLast(TIMEOUT); + if(requestBuilderCustomizer != null) { + requestBuilderCustomizer.accept(requestBuilder); + } return requestBuilder; } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClient.java index 82ad85870977..c5fc57de2aec 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClient.java @@ -16,16 +16,22 @@ package org.springframework.test.web.servlet.client; +import java.time.Duration; +import java.util.List; +import java.util.function.Consumer; import java.util.function.Supplier; import jakarta.servlet.Filter; import org.springframework.format.support.FormattingConversionService; -import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.http.HttpHeaders; +import org.springframework.http.codec.ClientCodecConfigurer; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.lang.Nullable; +import org.springframework.test.web.reactive.server.EntityExchangeResult; import org.springframework.test.web.reactive.server.ExchangeResult; import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.test.web.reactive.server.WebTestClientConfigurer; import org.springframework.test.web.servlet.DispatcherServletCustomizer; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -33,14 +39,21 @@ import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultHandler; import org.springframework.test.web.servlet.ResultMatcher; +import org.springframework.test.web.servlet.client.MockMvcWebTestClient.Builder; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder; import org.springframework.test.web.servlet.setup.MockMvcConfigurer; import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder; +import org.springframework.util.MultiValueMap; import org.springframework.validation.Validator; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.ExchangeStrategies; +import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.servlet.FlashMapManager; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerInterceptor; @@ -48,6 +61,7 @@ import org.springframework.web.servlet.View; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.util.UriBuilderFactory; import org.springframework.web.util.pattern.PathPatternParser; /** @@ -57,7 +71,7 @@ *

Provides static factory methods and specs to initialize {@code MockMvc} * to which the {@code WebTestClient} connects to. For example: *

- * WebTestClient client = MockMvcWebTestClient.bindToController(myController)
+ * MockMvcWebTestClient client = MockMvcWebTestClient.bindToController(myController)
  *         .controllerAdvice(myControllerAdvice)
  *         .validator(myValidator)
  *         .build()
@@ -65,7 +79,7 @@
  *
  * 

The client itself can also be configured. For example: *

- * WebTestClient client = MockMvcWebTestClient.bindToController(myController)
+ * MockMvcWebTestClient client = MockMvcWebTestClient.bindToController(myController)
  *         .validator(myValidator)
  *         .configureClient()
  *         .baseUrl("/path")
@@ -75,7 +89,108 @@
  * @author Rossen Stoyanchev
  * @since 5.3
  */
-public interface MockMvcWebTestClient {
+public interface MockMvcWebTestClient extends WebTestClient {
+	
+	/**
+	 * Return a builder to mutate properties of this web test client.
+	 */
+	Builder mutate();
+	
+	/**
+	 * Mutate the {@link MockMvcWebTestClient}, apply the given requestPostProcessor, and build
+	 * a new instance. Essentially a shortcut for:
+	 * 
+	 * mutate().requestPostProcessor(requestPostProcessor).build();
+	 * 
+ * @param requestPostProcessor the requestPostProcessor to apply + * @return the mutated test client + */ + MockMvcWebTestClient mutateWith(RequestPostProcessor requestPostProcessor); + + /** + * Mutate the {@link MockMvcWebTestClient}, apply the given configurer, and build + * a new instance. Essentially a shortcut for: + *
+	 * mutate().apply(configurer).build();
+	 * 
+ * @param configurer the configurer to apply + * @return the mutated test client + */ + MockMvcWebTestClient mutateWith(MockMvcWebTestClientConfigurer configurer); + + + /** + * 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}. + */ + interface Builder extends WebTestClient.Builder { + Builder mockMvc(MockMvc mockMvc); + + Builder requestPostProcessor(RequestPostProcessor requestPostProcessor); + + Builder requestPostProcessors(Consumer> requestPostProcessorsConsumer); + + Builder requestBuilderCustomizer(Consumer requestBuilderCustomizer); + + Builder requestBuilderCustomizers(Consumer>> requestBuilderCustomizersConsumer); + + Builder apply(MockMvcWebTestClientConfigurer configurer); + + /* Override methods to return covariant return type */ + @Override + Builder baseUrl(String baseUrl); + + @Override + Builder uriBuilderFactory(UriBuilderFactory uriBuilderFactory); + + @Override + Builder defaultHeader(String headerName, String... headerValues); + + @Override + Builder defaultHeaders(Consumer headersConsumer); + + @Override + Builder defaultCookie(String cookieName, String... cookieValues); + + @Override + Builder defaultCookies(Consumer> cookiesConsumer); + + @Override + Builder filter(ExchangeFilterFunction filter); + + @Override + Builder filters(Consumer> filtersConsumer); + + @Override + Builder entityExchangeResultConsumer(Consumer> consumer); + + @Override + Builder codecs(Consumer configurer); + + @Override + Builder exchangeStrategies(ExchangeStrategies strategies); + + @Override + @SuppressWarnings("deprecation") + Builder exchangeStrategies( + Consumer configurer); + + @Override + Builder responseTimeout(Duration timeout); + + @Override + Builder apply(WebTestClientConfigurer configurer); + + /** + * Build the {@link WebTestClient} instance. + */ + @Override + MockMvcWebTestClient build(); + + } + /** * Begin creating a {@link WebTestClient} by providing the {@code @Controller} @@ -99,14 +214,22 @@ static ControllerSpec bindToController(Object... controllers) { static MockMvcServerSpec bindToApplicationContext(WebApplicationContext context) { return new ApplicationContextMockMvcSpec(context); } - + /** * Begin creating a {@link WebTestClient} by providing an already * initialized {@link MockMvc} instance to use as the server. */ + @Deprecated // Changing the return type causes Spring Boot to throw in MockMvcAutoConfiguration static WebTestClient.Builder bindTo(MockMvc mockMvc) { - ClientHttpConnector connector = new MockMvcHttpConnector(mockMvc); - return WebTestClient.bindToServer(connector); + return bindToMockMvc(mockMvc); + } + + /** + * Begin creating a {@link WebTestClient} by providing an already + * initialized {@link MockMvc} instance to use as the server. + */ + static MockMvcWebTestClient.Builder bindToMockMvc(MockMvc mockMvc) { + return new DefaultMockMvcWebTestClientBuilder(mockMvc); } /** @@ -226,12 +349,12 @@ interface MockMvcServerSpec> { /** * Proceed to configure and build the test client. */ - WebTestClient.Builder configureClient(); + Builder configureClient(); /** * Shortcut to build the test client. */ - WebTestClient build(); + MockMvcWebTestClient build(); } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClientConfigurer.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClientConfigurer.java new file mode 100644 index 000000000000..4633fc16cff5 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClientConfigurer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.web.servlet.client; + +import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.lang.Nullable; +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; + +/** + * Contract that frameworks or applications can use to pre-package a set of + * customizations to a {@link MockMvcWebTestClient.Builder} and expose that + * as a shortcut. + * + * @author Rossen Stoyanchev + * @see MockServerConfigurer + */ +public interface MockMvcWebTestClientConfigurer { + + /** + * Invoked once only, immediately (i.e. before this method returns). + * @param builder the MockMvcWebTestClient builder to make changes to + * @param httpHandlerBuilder the builder for the "mock server" HttpHandler + * this client was configured for "mock server" testing + * @param connector the connector for "live" integration tests if this + * server was configured for live integration testing + */ + void afterConfigurerAdded(MockMvcWebTestClient.Builder builder, + @Nullable WebHttpHandlerBuilder httpHandlerBuilder, + @Nullable ClientHttpConnector connector); + +} From dca11d51992327e348942d5e315dd45500d6dca5 Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Thu, 30 Mar 2023 14:57:05 +0800 Subject: [PATCH 2/2] Fix checkstyle violations --- .../client/DefaultMockMvcWebTestClient.java | 23 ++- .../DefaultMockMvcWebTestClientBuilder.java | 53 ++++-- .../servlet/client/MockMvcHttpConnector.java | 10 +- .../servlet/client/MockMvcWebTestClient.java | 153 +++++++++--------- 4 files changed, 138 insertions(+), 101 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultMockMvcWebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultMockMvcWebTestClient.java index b49c18f48901..4f6adcd301c2 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultMockMvcWebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultMockMvcWebTestClient.java @@ -1,3 +1,19 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.test.web.servlet.client; import java.time.Duration; @@ -13,6 +29,11 @@ import org.springframework.web.reactive.function.client.ExchangeFunction; import org.springframework.web.util.UriBuilderFactory; +/** + * Default implementation of {@link MockMvcWebTestClient}. + * + * @author Justin Tay + */ public class DefaultMockMvcWebTestClient extends DefaultWebTestClient implements MockMvcWebTestClient { private final DefaultMockMvcWebTestClientBuilder builder; @@ -28,7 +49,7 @@ public class DefaultMockMvcWebTestClient extends DefaultWebTestClient implements @Override public MockMvcWebTestClient.Builder mutate() { - return new DefaultMockMvcWebTestClientBuilder(builder); + return new DefaultMockMvcWebTestClientBuilder(this.builder); } @Override diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultMockMvcWebTestClientBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultMockMvcWebTestClientBuilder.java index b647e52b3984..65e257aafd14 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultMockMvcWebTestClientBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultMockMvcWebTestClientBuilder.java @@ -1,3 +1,19 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.test.web.servlet.client; import java.time.Duration; @@ -24,12 +40,17 @@ import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.util.UriBuilderFactory; +/** + * Default implementation of {@link MockMvcWebTestClient.Builder}. + * + * @author Justin Tay + */ public class DefaultMockMvcWebTestClientBuilder extends DefaultWebTestClientBuilder implements MockMvcWebTestClient.Builder { - + private MockMvc mockMvc; - private List requestPostProcessors = new ArrayList<>(); + private List requestPostProcessors = new ArrayList<>(); private List> requestBuilderCustomizers = new ArrayList<>(); - + DefaultMockMvcWebTestClientBuilder(MockMvc mockMvc) { this.mockMvc = mockMvc; } @@ -41,48 +62,48 @@ public class DefaultMockMvcWebTestClientBuilder extends DefaultWebTestClientBuil this.requestPostProcessors = new ArrayList<>(other.requestPostProcessors); this.requestBuilderCustomizers = new ArrayList<>(other.requestBuilderCustomizers); } - + @Override public Builder mockMvc(MockMvc mockMvc) { this.mockMvc = mockMvc; return this; } - + @Override public Builder requestPostProcessors(Consumer> requestPostProcessorsConsumer) { - requestPostProcessorsConsumer.accept(requestPostProcessors); + requestPostProcessorsConsumer.accept(this.requestPostProcessors); return this; } @Override public Builder requestPostProcessor(RequestPostProcessor requestPostProcessor) { - requestPostProcessors.add(requestPostProcessor); + this.requestPostProcessors.add(requestPostProcessor); return this; } - + @Override public Builder requestBuilderCustomizer(Consumer requestBuilderCustomizer) { this.requestBuilderCustomizers.add(requestBuilderCustomizer); return this; } - + @Override public Builder requestBuilderCustomizers(Consumer>> requestBuilderCustomizersConsumer) { - requestBuilderCustomizersConsumer.accept(requestBuilderCustomizers); + requestBuilderCustomizersConsumer.accept(this.requestBuilderCustomizers); return this; } - + @Override public Builder apply(MockMvcWebTestClientConfigurer configurer) { configurer.afterConfigurerAdded(this, this.httpHandlerBuilder, this.connector); return this; } - + @Override public MockMvcWebTestClient build() { - ClientHttpConnector connectorToUse = new MockMvcHttpConnector(mockMvc, customizer -> { - requestPostProcessors.forEach(customizer::with); - requestBuilderCustomizers.forEach(builderCustomizer -> builderCustomizer.accept(customizer)); + ClientHttpConnector connectorToUse = new MockMvcHttpConnector(this.mockMvc, customizer -> { + this.requestPostProcessors.forEach(customizer::with); + this.requestBuilderCustomizers.forEach(builderCustomizer -> builderCustomizer.accept(customizer)); }); Function exchangeFactory = connector -> { ExchangeFunction exchange = ExchangeFunctions.create(connector, initExchangeStrategies()); @@ -100,7 +121,7 @@ public MockMvcWebTestClient build() { this.defaultCookies != null ? CollectionUtils.unmodifiableMultiValueMap(this.defaultCookies) : null, this.entityResultConsumer, this.responseTimeout, new DefaultMockMvcWebTestClientBuilder(this)); } - + /* Override methods to return covariant return type */ @Override public Builder baseUrl(String baseUrl) { diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java index 11d223ab4d22..42c07b98bfe4 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java @@ -82,7 +82,7 @@ public class MockMvcHttpConnector implements ClientHttpConnector { private final MockMvc mockMvc; - + private final Consumer requestBuilderCustomizer; public MockMvcHttpConnector(MockMvc mockMvc, Consumer requestBuilderCustomizer) { @@ -149,8 +149,8 @@ private MockHttpServletRequestBuilder initRequestBuilder( if (!ObjectUtils.isEmpty(bytes)) { requestBuilder.content(bytes); } - if(requestBuilderCustomizer != null) { - requestBuilderCustomizer.accept(requestBuilder); + if(this.requestBuilderCustomizer != null) { + this.requestBuilderCustomizer.accept(requestBuilder); } return requestBuilder; } @@ -180,8 +180,8 @@ private MockHttpServletRequestBuilder initRequestBuilder( })) .blockLast(TIMEOUT); - if(requestBuilderCustomizer != null) { - requestBuilderCustomizer.accept(requestBuilder); + if(this.requestBuilderCustomizer != null) { + this.requestBuilderCustomizer.accept(requestBuilder); } return requestBuilder; } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClient.java index c5fc57de2aec..021037231ac2 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClient.java @@ -39,7 +39,6 @@ import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultHandler; import org.springframework.test.web.servlet.ResultMatcher; -import org.springframework.test.web.servlet.client.MockMvcWebTestClient.Builder; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder; @@ -90,12 +89,12 @@ * @since 5.3 */ public interface MockMvcWebTestClient extends WebTestClient { - + /** * Return a builder to mutate properties of this web test client. */ Builder mutate(); - + /** * Mutate the {@link MockMvcWebTestClient}, apply the given requestPostProcessor, and build * a new instance. Essentially a shortcut for: @@ -106,7 +105,7 @@ public interface MockMvcWebTestClient extends WebTestClient { * @return the mutated test client */ MockMvcWebTestClient mutateWith(RequestPostProcessor requestPostProcessor); - + /** * Mutate the {@link MockMvcWebTestClient}, apply the given configurer, and build * a new instance. Essentially a shortcut for: @@ -118,80 +117,6 @@ public interface MockMvcWebTestClient extends WebTestClient { */ MockMvcWebTestClient mutateWith(MockMvcWebTestClientConfigurer configurer); - - /** - * 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}. - */ - interface Builder extends WebTestClient.Builder { - Builder mockMvc(MockMvc mockMvc); - - Builder requestPostProcessor(RequestPostProcessor requestPostProcessor); - - Builder requestPostProcessors(Consumer> requestPostProcessorsConsumer); - - Builder requestBuilderCustomizer(Consumer requestBuilderCustomizer); - - Builder requestBuilderCustomizers(Consumer>> requestBuilderCustomizersConsumer); - - Builder apply(MockMvcWebTestClientConfigurer configurer); - - /* Override methods to return covariant return type */ - @Override - Builder baseUrl(String baseUrl); - - @Override - Builder uriBuilderFactory(UriBuilderFactory uriBuilderFactory); - - @Override - Builder defaultHeader(String headerName, String... headerValues); - - @Override - Builder defaultHeaders(Consumer headersConsumer); - - @Override - Builder defaultCookie(String cookieName, String... cookieValues); - - @Override - Builder defaultCookies(Consumer> cookiesConsumer); - - @Override - Builder filter(ExchangeFilterFunction filter); - - @Override - Builder filters(Consumer> filtersConsumer); - - @Override - Builder entityExchangeResultConsumer(Consumer> consumer); - - @Override - Builder codecs(Consumer configurer); - - @Override - Builder exchangeStrategies(ExchangeStrategies strategies); - - @Override - @SuppressWarnings("deprecation") - Builder exchangeStrategies( - Consumer configurer); - - @Override - Builder responseTimeout(Duration timeout); - - @Override - Builder apply(WebTestClientConfigurer configurer); - - /** - * Build the {@link WebTestClient} instance. - */ - @Override - MockMvcWebTestClient build(); - - } - - /** * Begin creating a {@link WebTestClient} by providing the {@code @Controller} * instance(s) to handle requests with. @@ -214,7 +139,7 @@ static ControllerSpec bindToController(Object... controllers) { static MockMvcServerSpec bindToApplicationContext(WebApplicationContext context) { return new ApplicationContextMockMvcSpec(context); } - + /** * Begin creating a {@link WebTestClient} by providing an already * initialized {@link MockMvc} instance to use as the server. @@ -504,4 +429,74 @@ ControllerSpec mappedInterceptors( ControllerSpec customHandlerMapping(Supplier factory); } + /** + * 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}. + */ + interface Builder extends WebTestClient.Builder { + Builder mockMvc(MockMvc mockMvc); + + Builder requestPostProcessor(RequestPostProcessor requestPostProcessor); + + Builder requestPostProcessors(Consumer> requestPostProcessorsConsumer); + + Builder requestBuilderCustomizer(Consumer requestBuilderCustomizer); + + Builder requestBuilderCustomizers(Consumer>> requestBuilderCustomizersConsumer); + + Builder apply(MockMvcWebTestClientConfigurer configurer); + + /* Override methods to return covariant return type */ + @Override + Builder baseUrl(String baseUrl); + + @Override + Builder uriBuilderFactory(UriBuilderFactory uriBuilderFactory); + + @Override + Builder defaultHeader(String headerName, String... headerValues); + + @Override + Builder defaultHeaders(Consumer headersConsumer); + + @Override + Builder defaultCookie(String cookieName, String... cookieValues); + + @Override + Builder defaultCookies(Consumer> cookiesConsumer); + + @Override + Builder filter(ExchangeFilterFunction filter); + + @Override + Builder filters(Consumer> filtersConsumer); + + @Override + Builder entityExchangeResultConsumer(Consumer> consumer); + + @Override + Builder codecs(Consumer configurer); + + @Override + Builder exchangeStrategies(ExchangeStrategies strategies); + + @Override + @SuppressWarnings("deprecation") + Builder exchangeStrategies( + Consumer configurer); + + @Override + Builder responseTimeout(Duration timeout); + + @Override + Builder apply(WebTestClientConfigurer configurer); + + /** + * Build the {@link WebTestClient} instance. + */ + @Override + MockMvcWebTestClient build(); + } }