Add support for configuring SslInfo in WebTestClient

Prior to this commit, there was no easy way to configure an SslInfo
instance for use with WebTestClient.

To address that, this commit introduces a new sslInfo(SslInfo) method
in WebTestClient.MockServerSpec, which can be used as follows.

var client = WebTestClient.bindToApplicationContext(context)
                          .sslInfo(new MockSslInfo("mock ID"))
                          // ...
                          .build();

Closes gh-35042
This commit is contained in:
Sam Brannen
2025-06-18 11:04:36 +02:00
parent 4375e59a28
commit 1fb04cb83a
5 changed files with 94 additions and 10 deletions
@@ -22,6 +22,7 @@ import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.http.server.reactive.SslInfo;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
@@ -32,6 +33,7 @@ import org.springframework.web.server.session.WebSessionManager;
* Base class for implementations of {@link WebTestClient.MockServerSpec}.
*
* @author Rossen Stoyanchev
* @author Sam Brannen
* @since 5.0
* @param <B> a self reference to the builder type
*/
@@ -42,6 +44,8 @@ abstract class AbstractMockServerSpec<B extends WebTestClient.MockServerSpec<B>>
private @Nullable WebSessionManager sessionManager;
private @Nullable SslInfo sslInfo;
private @Nullable List<MockServerConfigurer> configurers;
@@ -66,6 +70,12 @@ abstract class AbstractMockServerSpec<B extends WebTestClient.MockServerSpec<B>>
return self();
}
@Override
public <T extends B> T sslInfo(SslInfo sslInfo) {
this.sslInfo = sslInfo;
return self();
}
@Override
public <T extends B> T apply(MockServerConfigurer configurer) {
configurer.afterConfigureAdded(this);
@@ -91,7 +101,7 @@ abstract class AbstractMockServerSpec<B extends WebTestClient.MockServerSpec<B>>
if (!CollectionUtils.isEmpty(this.configurers)) {
this.configurers.forEach(configurer -> configurer.beforeServerCreated(builder));
}
return new DefaultWebTestClientBuilder(builder);
return new DefaultWebTestClientBuilder(builder, this.sslInfo);
}
/**
@@ -32,6 +32,7 @@ import org.springframework.http.client.reactive.JdkClientHttpConnector;
import org.springframework.http.client.reactive.JettyClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.ClientCodecConfigurer;
import org.springframework.http.server.reactive.SslInfo;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
@@ -50,6 +51,7 @@ import org.springframework.web.util.UriBuilderFactory;
* Default implementation of {@link WebTestClient.Builder}.
*
* @author Rossen Stoyanchev
* @author Sam Brannen
* @since 5.0
*/
class DefaultWebTestClientBuilder implements WebTestClient.Builder {
@@ -78,6 +80,8 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
private @Nullable ClientHttpConnector connector;
private @Nullable SslInfo sslInfo;
private @Nullable String baseUrl;
private @Nullable UriBuilderFactory uriBuilderFactory;
@@ -103,21 +107,21 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
/** Determine connector via classpath detection. */
DefaultWebTestClientBuilder() {
this(null, null);
this(null, null, null);
}
/** Use HttpHandlerConnector with mock server. */
DefaultWebTestClientBuilder(WebHttpHandlerBuilder httpHandlerBuilder) {
this(httpHandlerBuilder, null);
DefaultWebTestClientBuilder(WebHttpHandlerBuilder httpHandlerBuilder, @Nullable SslInfo sslInfo) {
this(httpHandlerBuilder, null, sslInfo);
}
/** Use given connector. */
DefaultWebTestClientBuilder(ClientHttpConnector connector) {
this(null, connector);
this(null, connector, null);
}
DefaultWebTestClientBuilder(
@Nullable WebHttpHandlerBuilder httpHandlerBuilder, @Nullable ClientHttpConnector connector) {
private DefaultWebTestClientBuilder(@Nullable WebHttpHandlerBuilder httpHandlerBuilder,
@Nullable ClientHttpConnector connector, @Nullable SslInfo sslInfo) {
Assert.isTrue(httpHandlerBuilder == null || connector == null,
"Expected WebHttpHandlerBuilder or ClientHttpConnector but not both.");
@@ -127,6 +131,7 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
"To use WebTestClient, please add spring-webflux to the test classpath.");
this.connector = connector;
this.sslInfo = sslInfo;
this.httpHandlerBuilder = (httpHandlerBuilder != null ? httpHandlerBuilder.clone() : null);
}
@@ -134,6 +139,7 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
DefaultWebTestClientBuilder(DefaultWebTestClientBuilder other) {
this.httpHandlerBuilder = (other.httpHandlerBuilder != null ? other.httpHandlerBuilder.clone() : null);
this.connector = other.connector;
this.sslInfo = other.sslInfo;
this.responseTimeout = other.responseTimeout;
this.baseUrl = other.baseUrl;
@@ -284,7 +290,7 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
ClientHttpConnector connectorToUse = this.connector;
if (connectorToUse == null) {
if (this.httpHandlerBuilder != null) {
connectorToUse = new HttpHandlerConnector(this.httpHandlerBuilder.build());
connectorToUse = new HttpHandlerConnector(this.httpHandlerBuilder.build(), this.sslInfo);
}
}
if (connectorToUse == null) {
@@ -21,6 +21,7 @@ import java.util.function.Function;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -40,9 +41,11 @@ import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.HttpHeadResponseDecorator;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.SslInfo;
import org.springframework.mock.http.client.reactive.MockClientHttpRequest;
import org.springframework.mock.http.client.reactive.MockClientHttpResponse;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest.BodyBuilder;
import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
@@ -56,6 +59,7 @@ import org.springframework.util.MultiValueMap;
* {@link MockServerHttpRequest} and {@link MockServerHttpResponse}.
*
* @author Rossen Stoyanchev
* @author Sam Brannen
* @since 5.0
*/
public class HttpHandlerConnector implements ClientHttpConnector {
@@ -64,13 +68,26 @@ public class HttpHandlerConnector implements ClientHttpConnector {
private final HttpHandler handler;
private final @Nullable SslInfo sslInfo;
/**
* Constructor with the {@link HttpHandler} to handle requests with.
* Construct an {@code HttpHandlerConnector} with the supplied {@link HttpHandler}
* to handle requests with.
*/
public HttpHandlerConnector(HttpHandler handler) {
this(handler, null);
}
/**
* Construct an {@code HttpHandlerConnector} with the supplied {@link SslInfo}
* and {@link HttpHandler} to handle requests with.
* @since 7.0
*/
public HttpHandlerConnector(HttpHandler handler, @Nullable SslInfo sslInfo) {
Assert.notNull(handler, "HttpHandler is required");
this.handler = handler;
this.sslInfo = sslInfo;
}
@@ -136,7 +153,11 @@ public class HttpHandlerConnector implements ClientHttpConnector {
URI uri = request.getURI();
HttpHeaders headers = request.getHeaders();
MultiValueMap<String, HttpCookie> cookies = request.getCookies();
return MockServerHttpRequest.method(method, uri).headers(headers).cookies(cookies).body(body);
BodyBuilder builder = MockServerHttpRequest.method(method, uri).headers(headers).cookies(cookies);
if (this.sslInfo != null) {
builder.sslInfo(this.sslInfo);
}
return builder.body(body);
}
private ServerHttpResponse prepareResponse(ServerHttpResponse response, ServerHttpRequest request) {
@@ -40,6 +40,7 @@ import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.http.codec.ClientCodecConfigurer;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.SslInfo;
import org.springframework.test.json.JsonComparator;
import org.springframework.test.json.JsonCompareMode;
import org.springframework.test.json.JsonComparison;
@@ -275,6 +276,14 @@ public interface WebTestClient {
*/
<T extends B> T webSessionManager(WebSessionManager sessionManager);
/**
* Provide SSL session information and certificates for the mock server.
* @param sslInfo the {@link SslInfo} to use
* @since 7.0
* @see org.springframework.mock.http.server.reactive.MockSslInfo
*/
<T extends B> T sslInfo(SslInfo sslInfo);
/**
* Shortcut for pre-packaged customizations to the mock server setup.
* @param configurer the configurer to apply