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 f3c9e3650a..136cc93274 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -80,7 +80,7 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { private final WebHttpHandlerBuilder httpHandlerBuilder; @Nullable - private final ClientHttpConnector connector; + private ClientHttpConnector connector; @Nullable private String baseUrl; @@ -277,6 +277,12 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { return this; } + @Override + public WebTestClient.Builder clientConnector(ClientHttpConnector connector) { + this.connector = connector; + return this; + } + @Override public WebTestClient build() { ClientHttpConnector connectorToUse = this.connector; 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 5b264b1c9d..622c6f9d79 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 @@ -512,6 +512,18 @@ public interface WebTestClient { */ Builder responseTimeout(Duration timeout); + /** + * Set the {@link ClientHttpConnector} to use. + *

By default, this is initialized and set internally. However, the + * connector may also be prepared externally and passed via + * {@link WebTestClient#bindToServer(ClientHttpConnector)} such as for + * {@code MockMvcWebTestClient} tests, and in that case you can use this + * from {@link #mutateWith(WebTestClientConfigurer)} to replace it. + * @param connector the connector to use + * @since 6.1 + */ + Builder clientConnector(ClientHttpConnector connector); + /** * Apply the given configurer to this builder instance. *

This can be useful for applying pre-packaged customizations. 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 b7fa5dbd35..36a49b58c2 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 @@ -19,6 +19,7 @@ package org.springframework.test.web.servlet.client; import java.io.StringWriter; import java.net.URI; import java.time.Duration; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicReference; @@ -56,6 +57,7 @@ import org.springframework.test.web.servlet.RequestBuilder; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -82,9 +84,16 @@ public class MockMvcHttpConnector implements ClientHttpConnector { private final MockMvc mockMvc; + private final List requestPostProcessors; + public MockMvcHttpConnector(MockMvc mockMvc) { + this(mockMvc, Collections.emptyList()); + } + + private MockMvcHttpConnector(MockMvc mockMvc, List requestPostProcessors) { this.mockMvc = mockMvc; + this.requestPostProcessors = new ArrayList<>(requestPostProcessors); } @@ -135,6 +144,8 @@ public class MockMvcHttpConnector implements ClientHttpConnector { } } + this.requestPostProcessors.forEach(requestBuilder::with); + return requestBuilder; } @@ -207,6 +218,15 @@ public class MockMvcHttpConnector implements ClientHttpConnector { return clientResponse; } + /** + * Create a new instance that applies the given {@link RequestPostProcessor}s + * to performed requests. + * @since 6.1 + */ + public MockMvcHttpConnector with(List postProcessors) { + return new MockMvcHttpConnector(this.mockMvc, postProcessors); + } + private static class MockMvcServerClientHttpResponse extends MockClientHttpResponse implements MockServerClientHttpResponse { diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/FrameworkExtensionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/FrameworkExtensionTests.java index ad9efb79ca..bfbc8d6a10 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/FrameworkExtensionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/FrameworkExtensionTests.java @@ -17,11 +17,17 @@ package org.springframework.test.web.servlet.samples.client.standalone; import java.security.Principal; +import java.util.List; import org.junit.jupiter.api.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.stereotype.Controller; import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.test.web.reactive.server.WebTestClientConfigurer; +import org.springframework.test.web.servlet.client.MockMvcHttpConnector; import org.springframework.test.web.servlet.client.MockMvcWebTestClient; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder; @@ -30,6 +36,7 @@ import org.springframework.util.Assert; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import static org.mockito.Mockito.mock; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -50,16 +57,16 @@ public class FrameworkExtensionTests { @Test public void fooHeader() { - this.client.get().uri("/") - .header("Foo", "a=b") + this.client.mutateWith(headers().foo("a=b")) + .get().uri("/") .exchange() .expectBody(String.class).isEqualTo("Foo"); } @Test public void barHeader() { - this.client.get().uri("/") - .header("Bar", "a=b") + this.client.mutateWith(headers().bar("a=b")) + .get().uri("/") .exchange() .expectBody(String.class).isEqualTo("Bar"); } @@ -68,6 +75,68 @@ public class FrameworkExtensionTests { return new TestMockMvcConfigurer(); } + private static TestWebTestClientConfigurer headers() { + return new TestWebTestClientConfigurer(); + } + + + /** + * Test WebTestClientConfigurer that re-creates the MockMvcHttpConnector + * with a {@code TestRequestPostProcessor}. + */ + private static class TestWebTestClientConfigurer implements WebTestClientConfigurer { + + private final TestRequestPostProcessor requestPostProcessor = new TestRequestPostProcessor(); + + public TestWebTestClientConfigurer foo(String value) { + this.requestPostProcessor.foo(value); + return this; + } + + public TestWebTestClientConfigurer bar(String value) { + this.requestPostProcessor.bar(value); + return this; + } + + @Override + public void afterConfigurerAdded( + WebTestClient.Builder builder, WebHttpHandlerBuilder httpHandlerBuilder, + ClientHttpConnector connector) { + + if (connector instanceof MockMvcHttpConnector mockMvcConnector) { + builder.clientConnector(mockMvcConnector.with(List.of(this.requestPostProcessor))); + } + } + } + + + /** + * Test {@code RequestPostProcessor} for custom headers. + */ + private static class TestRequestPostProcessor implements RequestPostProcessor { + + private final HttpHeaders headers = new HttpHeaders(); + + + public TestRequestPostProcessor foo(String value) { + this.headers.add("Foo", value); + return this; + } + + public TestRequestPostProcessor bar(String value) { + this.headers.add("Bar", value); + return this; + } + + @Override + public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { + for (String headerName : this.headers.keySet()) { + request.addHeader(headerName, this.headers.get(headerName)); + } + return request; + } + } + /** * Test {@code MockMvcConfigurer}. @@ -80,8 +149,9 @@ public class FrameworkExtensionTests { } @Override - public RequestPostProcessor beforeMockMvcCreated(ConfigurableMockMvcBuilder builder, - WebApplicationContext context) { + public RequestPostProcessor beforeMockMvcCreated( + ConfigurableMockMvcBuilder builder, WebApplicationContext context) { + return request -> { request.setUserPrincipal(mock()); return request; diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/FrameworkExtensionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/FrameworkExtensionTests.java index 4373e133b4..0af7bbccce 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/FrameworkExtensionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/FrameworkExtensionTests.java @@ -18,7 +18,6 @@ package org.springframework.test.web.servlet.samples.standalone; import java.security.Principal; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; @@ -53,14 +52,9 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal */ public class FrameworkExtensionTests { - private MockMvc mockMvc; + private final MockMvc mockMvc = standaloneSetup(new SampleController()).apply(defaultSetup()).build(); - @BeforeEach - public void setup() { - this.mockMvc = standaloneSetup(new SampleController()).apply(defaultSetup()).build(); - } - @Test public void fooHeader() throws Exception { this.mockMvc.perform(get("/").with(headers().foo("a=b"))).andExpect(content().string("Foo")); @@ -81,7 +75,7 @@ public class FrameworkExtensionTests { /** - * Test {@code RequestPostProcessor}. + * Test {@code RequestPostProcessor} for custom headers. */ private static class TestRequestPostProcessor implements RequestPostProcessor { @@ -119,8 +113,9 @@ public class FrameworkExtensionTests { } @Override - public RequestPostProcessor beforeMockMvcCreated(ConfigurableMockMvcBuilder builder, - WebApplicationContext context) { + public RequestPostProcessor beforeMockMvcCreated( + ConfigurableMockMvcBuilder builder, WebApplicationContext context) { + return request -> { request.setUserPrincipal(mock()); return request;