Update RestTestClient builder hierarchy

Add concrete classes with specified generics for each MockMvc setup
Ensure Builder methods return the concrete class

See gh-34428
This commit is contained in:
rstoyanchev
2025-07-29 10:17:43 +01:00
parent db4696ceae
commit 2732b603dc
13 changed files with 131 additions and 100 deletions
@@ -155,7 +155,7 @@ public interface WebTestClient {
/**
* Return a builder to mutate properties of this web test client.
* Return a builder to mutate properties of this test client.
*/
Builder mutate();
@@ -171,8 +171,6 @@ public interface WebTestClient {
WebTestClient mutateWith(WebTestClientConfigurer configurer);
// Static factory methods
/**
* Use this server setup to test one {@code @Controller} at a time.
* This option loads the default configuration of
@@ -1,49 +0,0 @@
/*
* Copyright 2002-present 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.util.function.Consumer;
import org.springframework.test.web.servlet.MockMvcBuilder;
/**
* Default implementation of {@link RestTestClient.MockServerBuilder}.
* @author Rob Worsnop
* @param <M> the type of the {@link MockMvcBuilder} to use for building the mock server
*/
class DefaultMockServerBuilder<M extends MockMvcBuilder>
extends DefaultRestTestClientBuilder<RestTestClient.MockServerBuilder<M>>
implements RestTestClient.MockServerBuilder<M> {
private final M builder;
public DefaultMockServerBuilder(M builder) {
this.builder = builder;
}
@Override
public RestTestClient.MockServerBuilder<M> configureServer(Consumer<M> consumer) {
consumer.accept(this.builder);
return this;
}
@Override
public RestTestClient build() {
this.restClientBuilder.requestFactory(new MockMvcClientHttpRequestFactory(this.builder.build()));
return super.build();
}
}
@@ -18,9 +18,20 @@ package org.springframework.test.web.servlet.client;
import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MockMvcBuilder;
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.MultiValueMap;
import org.springframework.web.client.RestClient;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.util.UriBuilderFactory;
/**
@@ -33,7 +44,7 @@ import org.springframework.web.util.UriBuilderFactory;
*/
class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implements RestTestClient.Builder<B> {
protected final RestClient.Builder restClientBuilder;
private final RestClient.Builder restClientBuilder;
DefaultRestTestClientBuilder() {
@@ -46,49 +57,110 @@ class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implemen
@Override
public RestTestClient.Builder<B> baseUrl(String baseUrl) {
public <T extends B> T baseUrl(String baseUrl) {
this.restClientBuilder.baseUrl(baseUrl);
return this;
return self();
}
@Override
public RestTestClient.Builder<B> uriBuilderFactory(UriBuilderFactory uriFactory) {
public <T extends B> T uriBuilderFactory(UriBuilderFactory uriFactory) {
this.restClientBuilder.uriBuilderFactory(uriFactory);
return this;
return self();
}
@Override
public RestTestClient.Builder<B> defaultHeader(String headerName, String... headerValues) {
public <T extends B> T defaultHeader(String headerName, String... headerValues) {
this.restClientBuilder.defaultHeader(headerName, headerValues);
return this;
return self();
}
@Override
public RestTestClient.Builder<B> defaultHeaders(Consumer<HttpHeaders> headersConsumer) {
public <T extends B> T defaultHeaders(Consumer<HttpHeaders> headersConsumer) {
this.restClientBuilder.defaultHeaders(headersConsumer);
return this;
return self();
}
@Override
public RestTestClient.Builder<B> defaultCookie(String cookieName, String... cookieValues) {
public <T extends B> T defaultCookie(String cookieName, String... cookieValues) {
this.restClientBuilder.defaultCookie(cookieName, cookieValues);
return this;
return self();
}
@Override
public RestTestClient.Builder<B> defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer) {
public <T extends B> T defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer) {
this.restClientBuilder.defaultCookies(cookiesConsumer);
return this;
return self();
}
@Override
public RestTestClient.Builder<B> apply(Consumer<RestTestClient.Builder<B>> builderConsumer) {
public <T extends B> T apply(Consumer<RestTestClient.Builder<B>> builderConsumer) {
builderConsumer.accept(this);
return this;
return self();
}
@SuppressWarnings("unchecked")
protected <T extends B> T self() {
return (T) this;
}
protected void setClientHttpRequestFactory(ClientHttpRequestFactory requestFactory) {
this.restClientBuilder.requestFactory(requestFactory);
}
@Override
public RestTestClient build() {
return new DefaultRestTestClient(this.restClientBuilder);
}
static class AbstractMockMvcSetupBuilder<S extends RestTestClient.Builder<S>, M extends MockMvcBuilder>
extends DefaultRestTestClientBuilder<S> implements RestTestClient.MockMvcSetupBuilder<S, M> {
private final M mockMvcBuilder;
public AbstractMockMvcSetupBuilder(M mockMvcBuilder) {
this.mockMvcBuilder = mockMvcBuilder;
}
public <T extends S> T configureServer(Consumer<M> consumer) {
consumer.accept(this.mockMvcBuilder);
return self();
}
@Override
public RestTestClient build() {
MockMvc mockMvc = this.mockMvcBuilder.build();
setClientHttpRequestFactory(new MockMvcClientHttpRequestFactory(mockMvc));
return super.build();
}
}
static class DefaultStandaloneSetupBuilder extends AbstractMockMvcSetupBuilder<RestTestClient.StandaloneSetupBuilder, StandaloneMockMvcBuilder>
implements RestTestClient.StandaloneSetupBuilder {
DefaultStandaloneSetupBuilder(Object... controllers) {
super(MockMvcBuilders.standaloneSetup(controllers));
}
}
static class DefaultRouterFunctionSetupBuilder extends AbstractMockMvcSetupBuilder<RestTestClient.RouterFunctionSetupBuilder, RouterFunctionMockMvcBuilder>
implements RestTestClient.RouterFunctionSetupBuilder {
DefaultRouterFunctionSetupBuilder(RouterFunction<?>... routerFunctions) {
super(MockMvcBuilders.routerFunctions(routerFunctions));
}
}
static class DefaultWebAppContextSetupBuilder extends AbstractMockMvcSetupBuilder<RestTestClient.WebAppContextSetupBuilder, DefaultMockMvcBuilder>
implements RestTestClient.WebAppContextSetupBuilder {
DefaultWebAppContextSetupBuilder(WebApplicationContext context) {
super(MockMvcBuilders.webAppContextSetup(context));
}
}
}
@@ -37,7 +37,6 @@ import org.springframework.test.json.JsonComparison;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MockMvcBuilder;
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.MultiValueMap;
@@ -51,6 +50,8 @@ import org.springframework.web.util.UriBuilderFactory;
* Client for testing web servers.
*
* @author Rob Worsnop
* @author Rossen Stoyanchev
* @since 7.0
*/
public interface RestTestClient {
@@ -126,9 +127,8 @@ public interface RestTestClient {
* {@link org.springframework.test.web.servlet.setup.MockMvcBuilders#standaloneSetup(Object...)}
* to initialize {@link MockMvc}.
*/
static MockServerBuilder<StandaloneMockMvcBuilder> standaloneSetup(Object... controllers) {
StandaloneMockMvcBuilder builder = MockMvcBuilders.standaloneSetup(controllers);
return new DefaultMockServerBuilder<>(builder);
static StandaloneSetupBuilder bindToController(Object... controllers) {
return new DefaultRestTestClientBuilder.DefaultStandaloneSetupBuilder(controllers);
}
/**
@@ -138,9 +138,8 @@ public interface RestTestClient {
* {@link org.springframework.test.web.servlet.setup.MockMvcBuilders#routerFunctions(RouterFunction[])}
* to initialize {@link MockMvc}.
*/
static MockServerBuilder<RouterFunctionMockMvcBuilder> bindToRouterFunction(RouterFunction<?>... routerFunctions) {
RouterFunctionMockMvcBuilder builder = MockMvcBuilders.routerFunctions(routerFunctions);
return new DefaultMockServerBuilder<>(builder);
static RouterFunctionSetupBuilder bindToRouterFunction(RouterFunction<?>... routerFunctions) {
return new DefaultRestTestClientBuilder.DefaultRouterFunctionSetupBuilder(routerFunctions);
}
/**
@@ -151,16 +150,15 @@ public interface RestTestClient {
* {@link org.springframework.test.web.servlet.setup.MockMvcBuilders#webAppContextSetup(WebApplicationContext)}
* to initialize {@code MockMvc}.
*/
static MockServerBuilder<DefaultMockMvcBuilder> bindToApplicationContext(WebApplicationContext context) {
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(context);
return new DefaultMockServerBuilder<>(builder);
static WebAppContextSetupBuilder bindToApplicationContext(WebApplicationContext context) {
return new DefaultRestTestClientBuilder.DefaultWebAppContextSetupBuilder(context);
}
/**
* Begin creating a {@link RestTestClient} by providing an already
* initialized {@link MockMvc} instance to use as the server.
*/
static <B extends Builder<B>> Builder<B> bindTo(MockMvc mockMvc) {
static Builder<?> bindTo(MockMvc mockMvc) {
ClientHttpRequestFactory requestFactory = new MockMvcClientHttpRequestFactory(mockMvc);
return RestTestClient.bindToServer(requestFactory);
}
@@ -175,7 +173,7 @@ public interface RestTestClient {
* </pre>
* @return chained API to customize client config
*/
static <B extends Builder<B>> Builder<B> bindToServer() {
static Builder<?> bindToServer() {
return new DefaultRestTestClientBuilder<>();
}
@@ -183,7 +181,7 @@ public interface RestTestClient {
* A variant of {@link #bindToServer()} with a pre-configured request factory.
* @return chained API to customize client config
*/
static <B extends Builder<B>> Builder<B> bindToServer(ClientHttpRequestFactory requestFactory) {
static Builder<?> bindToServer(ClientHttpRequestFactory requestFactory) {
return new DefaultRestTestClientBuilder<>(RestClient.builder().requestFactory(requestFactory));
}
@@ -195,20 +193,20 @@ public interface RestTestClient {
* {@link RestClient#create(String)
* WebClient.create(String)}.
*/
Builder<B> baseUrl(String baseUrl);
<T extends B> T baseUrl(String baseUrl);
/**
* Provide a pre-configured {@link UriBuilderFactory} instance as an
* alternative to and effectively overriding {@link #baseUrl(String)}.
*/
Builder<B> uriBuilderFactory(UriBuilderFactory uriBuilderFactory);
<T extends B> T uriBuilderFactory(UriBuilderFactory uriBuilderFactory);
/**
* Add the given header to all requests that haven't added it.
* @param headerName the header name
* @param headerValues the header values
*/
Builder<B> defaultHeader(String headerName, String... headerValues);
<T extends B> T defaultHeader(String headerName, String... headerValues);
/**
* Manipulate the default headers with the given consumer. The
@@ -219,14 +217,14 @@ public interface RestTestClient {
* @param headersConsumer a function that consumes the {@code HttpHeaders}
* @return this builder
*/
Builder<B> defaultHeaders(Consumer<HttpHeaders> headersConsumer);
<T extends B> T defaultHeaders(Consumer<HttpHeaders> headersConsumer);
/**
* Add the given cookie to all requests.
* @param cookieName the cookie name
* @param cookieValues the cookie values
*/
Builder<B> defaultCookie(String cookieName, String... cookieValues);
<T extends B> T defaultCookie(String cookieName, String... cookieValues);
/**
* Manipulate the default cookies with the given consumer. The
@@ -237,29 +235,41 @@ public interface RestTestClient {
* @param cookiesConsumer a function that consumes the cookies map
* @return this builder
*/
Builder<B> defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
<T extends B> T defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
/**
* Apply the given {@code Consumer} to this builder instance.
* <p>This can be useful for applying pre-packaged customizations.
* @param builderConsumer the consumer to apply
*/
Builder<B> apply(Consumer<Builder<B>> builderConsumer);
<T extends B> T apply(Consumer<Builder<B>> builderConsumer);
/**
* Build the {@link RestTestClient} instance.
*/
RestTestClient build();
}
interface MockMvcSetupBuilder<B extends Builder<B>, M extends MockMvcBuilder> extends Builder<B> {
<T extends B> T configureServer(Consumer<M> consumer);
}
interface MockServerBuilder<M extends MockMvcBuilder> extends Builder<MockServerBuilder<M>> {
MockServerBuilder<M> configureServer(Consumer<M> consumer);
interface StandaloneSetupBuilder extends MockMvcSetupBuilder<StandaloneSetupBuilder, StandaloneMockMvcBuilder> {
}
interface RouterFunctionSetupBuilder extends MockMvcSetupBuilder<RouterFunctionSetupBuilder, RouterFunctionMockMvcBuilder> {
}
interface WebAppContextSetupBuilder extends MockMvcSetupBuilder<WebAppContextSetupBuilder, DefaultMockMvcBuilder> {
}
/**
* Specification for providing the URI of a request.
*