Skip to content

Commit 2732b60

Browse files
committed
Update RestTestClient builder hierarchy
Add concrete classes with specified generics for each MockMvc setup Ensure Builder methods return the concrete class See gh-34428
1 parent db4696c commit 2732b60

File tree

13 files changed

+129
-98
lines changed

13 files changed

+129
-98
lines changed

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public interface WebTestClient {
155155

156156

157157
/**
158-
* Return a builder to mutate properties of this web test client.
158+
* Return a builder to mutate properties of this test client.
159159
*/
160160
Builder mutate();
161161

@@ -171,8 +171,6 @@ public interface WebTestClient {
171171
WebTestClient mutateWith(WebTestClientConfigurer configurer);
172172

173173

174-
// Static factory methods
175-
176174
/**
177175
* Use this server setup to test one {@code @Controller} at a time.
178176
* This option loads the default configuration of

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

Lines changed: 0 additions & 49 deletions
This file was deleted.

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

Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,20 @@
1818

1919
import java.util.function.Consumer;
2020

21+
import org.jspecify.annotations.Nullable;
22+
2123
import org.springframework.http.HttpHeaders;
24+
import org.springframework.http.client.ClientHttpRequestFactory;
25+
import org.springframework.test.web.servlet.MockMvc;
26+
import org.springframework.test.web.servlet.MockMvcBuilder;
27+
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
28+
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
29+
import org.springframework.test.web.servlet.setup.RouterFunctionMockMvcBuilder;
30+
import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder;
2231
import org.springframework.util.MultiValueMap;
2332
import org.springframework.web.client.RestClient;
33+
import org.springframework.web.context.WebApplicationContext;
34+
import org.springframework.web.servlet.function.RouterFunction;
2435
import org.springframework.web.util.UriBuilderFactory;
2536

2637
/**
@@ -33,7 +44,7 @@
3344
*/
3445
class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implements RestTestClient.Builder<B> {
3546

36-
protected final RestClient.Builder restClientBuilder;
47+
private final RestClient.Builder restClientBuilder;
3748

3849

3950
DefaultRestTestClientBuilder() {
@@ -46,49 +57,110 @@ class DefaultRestTestClientBuilder<B extends RestTestClient.Builder<B>> implemen
4657

4758

4859
@Override
49-
public RestTestClient.Builder<B> baseUrl(String baseUrl) {
60+
public <T extends B> T baseUrl(String baseUrl) {
5061
this.restClientBuilder.baseUrl(baseUrl);
51-
return this;
62+
return self();
5263
}
5364

5465
@Override
55-
public RestTestClient.Builder<B> uriBuilderFactory(UriBuilderFactory uriFactory) {
66+
public <T extends B> T uriBuilderFactory(UriBuilderFactory uriFactory) {
5667
this.restClientBuilder.uriBuilderFactory(uriFactory);
57-
return this;
68+
return self();
5869
}
5970

6071
@Override
61-
public RestTestClient.Builder<B> defaultHeader(String headerName, String... headerValues) {
72+
public <T extends B> T defaultHeader(String headerName, String... headerValues) {
6273
this.restClientBuilder.defaultHeader(headerName, headerValues);
63-
return this;
74+
return self();
6475
}
6576

6677
@Override
67-
public RestTestClient.Builder<B> defaultHeaders(Consumer<HttpHeaders> headersConsumer) {
78+
public <T extends B> T defaultHeaders(Consumer<HttpHeaders> headersConsumer) {
6879
this.restClientBuilder.defaultHeaders(headersConsumer);
69-
return this;
80+
return self();
7081
}
7182

7283
@Override
73-
public RestTestClient.Builder<B> defaultCookie(String cookieName, String... cookieValues) {
84+
public <T extends B> T defaultCookie(String cookieName, String... cookieValues) {
7485
this.restClientBuilder.defaultCookie(cookieName, cookieValues);
75-
return this;
86+
return self();
7687
}
7788

7889
@Override
79-
public RestTestClient.Builder<B> defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer) {
90+
public <T extends B> T defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer) {
8091
this.restClientBuilder.defaultCookies(cookiesConsumer);
81-
return this;
92+
return self();
8293
}
8394

8495
@Override
85-
public RestTestClient.Builder<B> apply(Consumer<RestTestClient.Builder<B>> builderConsumer) {
96+
public <T extends B> T apply(Consumer<RestTestClient.Builder<B>> builderConsumer) {
8697
builderConsumer.accept(this);
87-
return this;
98+
return self();
99+
}
100+
101+
@SuppressWarnings("unchecked")
102+
protected <T extends B> T self() {
103+
return (T) this;
104+
}
105+
106+
protected void setClientHttpRequestFactory(ClientHttpRequestFactory requestFactory) {
107+
this.restClientBuilder.requestFactory(requestFactory);
88108
}
89109

90110
@Override
91111
public RestTestClient build() {
92112
return new DefaultRestTestClient(this.restClientBuilder);
93113
}
114+
115+
116+
static class AbstractMockMvcSetupBuilder<S extends RestTestClient.Builder<S>, M extends MockMvcBuilder>
117+
extends DefaultRestTestClientBuilder<S> implements RestTestClient.MockMvcSetupBuilder<S, M> {
118+
119+
private final M mockMvcBuilder;
120+
121+
public AbstractMockMvcSetupBuilder(M mockMvcBuilder) {
122+
this.mockMvcBuilder = mockMvcBuilder;
123+
}
124+
125+
public <T extends S> T configureServer(Consumer<M> consumer) {
126+
consumer.accept(this.mockMvcBuilder);
127+
return self();
128+
}
129+
130+
@Override
131+
public RestTestClient build() {
132+
MockMvc mockMvc = this.mockMvcBuilder.build();
133+
setClientHttpRequestFactory(new MockMvcClientHttpRequestFactory(mockMvc));
134+
return super.build();
135+
}
136+
}
137+
138+
139+
static class DefaultStandaloneSetupBuilder extends AbstractMockMvcSetupBuilder<RestTestClient.StandaloneSetupBuilder, StandaloneMockMvcBuilder>
140+
implements RestTestClient.StandaloneSetupBuilder {
141+
142+
DefaultStandaloneSetupBuilder(Object... controllers) {
143+
super(MockMvcBuilders.standaloneSetup(controllers));
144+
}
145+
}
146+
147+
148+
static class DefaultRouterFunctionSetupBuilder extends AbstractMockMvcSetupBuilder<RestTestClient.RouterFunctionSetupBuilder, RouterFunctionMockMvcBuilder>
149+
implements RestTestClient.RouterFunctionSetupBuilder {
150+
151+
DefaultRouterFunctionSetupBuilder(RouterFunction<?>... routerFunctions) {
152+
super(MockMvcBuilders.routerFunctions(routerFunctions));
153+
}
154+
155+
}
156+
157+
158+
static class DefaultWebAppContextSetupBuilder extends AbstractMockMvcSetupBuilder<RestTestClient.WebAppContextSetupBuilder, DefaultMockMvcBuilder>
159+
implements RestTestClient.WebAppContextSetupBuilder {
160+
161+
DefaultWebAppContextSetupBuilder(WebApplicationContext context) {
162+
super(MockMvcBuilders.webAppContextSetup(context));
163+
}
164+
}
165+
94166
}

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

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import org.springframework.test.web.servlet.MockMvc;
3838
import org.springframework.test.web.servlet.MockMvcBuilder;
3939
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
40-
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
4140
import org.springframework.test.web.servlet.setup.RouterFunctionMockMvcBuilder;
4241
import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder;
4342
import org.springframework.util.MultiValueMap;
@@ -51,6 +50,8 @@
5150
* Client for testing web servers.
5251
*
5352
* @author Rob Worsnop
53+
* @author Rossen Stoyanchev
54+
* @since 7.0
5455
*/
5556
public interface RestTestClient {
5657

@@ -126,9 +127,8 @@ public interface RestTestClient {
126127
* {@link org.springframework.test.web.servlet.setup.MockMvcBuilders#standaloneSetup(Object...)}
127128
* to initialize {@link MockMvc}.
128129
*/
129-
static MockServerBuilder<StandaloneMockMvcBuilder> standaloneSetup(Object... controllers) {
130-
StandaloneMockMvcBuilder builder = MockMvcBuilders.standaloneSetup(controllers);
131-
return new DefaultMockServerBuilder<>(builder);
130+
static StandaloneSetupBuilder bindToController(Object... controllers) {
131+
return new DefaultRestTestClientBuilder.DefaultStandaloneSetupBuilder(controllers);
132132
}
133133

134134
/**
@@ -138,9 +138,8 @@ static MockServerBuilder<StandaloneMockMvcBuilder> standaloneSetup(Object... con
138138
* {@link org.springframework.test.web.servlet.setup.MockMvcBuilders#routerFunctions(RouterFunction[])}
139139
* to initialize {@link MockMvc}.
140140
*/
141-
static MockServerBuilder<RouterFunctionMockMvcBuilder> bindToRouterFunction(RouterFunction<?>... routerFunctions) {
142-
RouterFunctionMockMvcBuilder builder = MockMvcBuilders.routerFunctions(routerFunctions);
143-
return new DefaultMockServerBuilder<>(builder);
141+
static RouterFunctionSetupBuilder bindToRouterFunction(RouterFunction<?>... routerFunctions) {
142+
return new DefaultRestTestClientBuilder.DefaultRouterFunctionSetupBuilder(routerFunctions);
144143
}
145144

146145
/**
@@ -151,16 +150,15 @@ static MockServerBuilder<RouterFunctionMockMvcBuilder> bindToRouterFunction(Rout
151150
* {@link org.springframework.test.web.servlet.setup.MockMvcBuilders#webAppContextSetup(WebApplicationContext)}
152151
* to initialize {@code MockMvc}.
153152
*/
154-
static MockServerBuilder<DefaultMockMvcBuilder> bindToApplicationContext(WebApplicationContext context) {
155-
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(context);
156-
return new DefaultMockServerBuilder<>(builder);
153+
static WebAppContextSetupBuilder bindToApplicationContext(WebApplicationContext context) {
154+
return new DefaultRestTestClientBuilder.DefaultWebAppContextSetupBuilder(context);
157155
}
158156

159157
/**
160158
* Begin creating a {@link RestTestClient} by providing an already
161159
* initialized {@link MockMvc} instance to use as the server.
162160
*/
163-
static <B extends Builder<B>> Builder<B> bindTo(MockMvc mockMvc) {
161+
static Builder<?> bindTo(MockMvc mockMvc) {
164162
ClientHttpRequestFactory requestFactory = new MockMvcClientHttpRequestFactory(mockMvc);
165163
return RestTestClient.bindToServer(requestFactory);
166164
}
@@ -175,15 +173,15 @@ static <B extends Builder<B>> Builder<B> bindTo(MockMvc mockMvc) {
175173
* </pre>
176174
* @return chained API to customize client config
177175
*/
178-
static <B extends Builder<B>> Builder<B> bindToServer() {
176+
static Builder<?> bindToServer() {
179177
return new DefaultRestTestClientBuilder<>();
180178
}
181179

182180
/**
183181
* A variant of {@link #bindToServer()} with a pre-configured request factory.
184182
* @return chained API to customize client config
185183
*/
186-
static <B extends Builder<B>> Builder<B> bindToServer(ClientHttpRequestFactory requestFactory) {
184+
static Builder<?> bindToServer(ClientHttpRequestFactory requestFactory) {
187185
return new DefaultRestTestClientBuilder<>(RestClient.builder().requestFactory(requestFactory));
188186
}
189187

@@ -195,20 +193,20 @@ interface Builder<B extends Builder<B>> {
195193
* {@link RestClient#create(String)
196194
* WebClient.create(String)}.
197195
*/
198-
Builder<B> baseUrl(String baseUrl);
196+
<T extends B> T baseUrl(String baseUrl);
199197

200198
/**
201199
* Provide a pre-configured {@link UriBuilderFactory} instance as an
202200
* alternative to and effectively overriding {@link #baseUrl(String)}.
203201
*/
204-
Builder<B> uriBuilderFactory(UriBuilderFactory uriBuilderFactory);
202+
<T extends B> T uriBuilderFactory(UriBuilderFactory uriBuilderFactory);
205203

206204
/**
207205
* Add the given header to all requests that haven't added it.
208206
* @param headerName the header name
209207
* @param headerValues the header values
210208
*/
211-
Builder<B> defaultHeader(String headerName, String... headerValues);
209+
<T extends B> T defaultHeader(String headerName, String... headerValues);
212210

213211
/**
214212
* Manipulate the default headers with the given consumer. The
@@ -219,14 +217,14 @@ interface Builder<B extends Builder<B>> {
219217
* @param headersConsumer a function that consumes the {@code HttpHeaders}
220218
* @return this builder
221219
*/
222-
Builder<B> defaultHeaders(Consumer<HttpHeaders> headersConsumer);
220+
<T extends B> T defaultHeaders(Consumer<HttpHeaders> headersConsumer);
223221

224222
/**
225223
* Add the given cookie to all requests.
226224
* @param cookieName the cookie name
227225
* @param cookieValues the cookie values
228226
*/
229-
Builder<B> defaultCookie(String cookieName, String... cookieValues);
227+
<T extends B> T defaultCookie(String cookieName, String... cookieValues);
230228

231229
/**
232230
* Manipulate the default cookies with the given consumer. The
@@ -237,29 +235,41 @@ interface Builder<B extends Builder<B>> {
237235
* @param cookiesConsumer a function that consumes the cookies map
238236
* @return this builder
239237
*/
240-
Builder<B> defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
238+
<T extends B> T defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
241239

242240
/**
243241
* Apply the given {@code Consumer} to this builder instance.
244242
* <p>This can be useful for applying pre-packaged customizations.
245243
* @param builderConsumer the consumer to apply
246244
*/
247-
Builder<B> apply(Consumer<Builder<B>> builderConsumer);
245+
<T extends B> T apply(Consumer<Builder<B>> builderConsumer);
248246

249247
/**
250248
* Build the {@link RestTestClient} instance.
251249
*/
252250
RestTestClient build();
251+
}
252+
253+
254+
interface MockMvcSetupBuilder<B extends Builder<B>, M extends MockMvcBuilder> extends Builder<B> {
255+
256+
<T extends B> T configureServer(Consumer<M> consumer);
257+
}
258+
259+
260+
interface StandaloneSetupBuilder extends MockMvcSetupBuilder<StandaloneSetupBuilder, StandaloneMockMvcBuilder> {
253261
}
254262

255263

256-
interface MockServerBuilder<M extends MockMvcBuilder> extends Builder<MockServerBuilder<M>> {
264+
interface RouterFunctionSetupBuilder extends MockMvcSetupBuilder<RouterFunctionSetupBuilder, RouterFunctionMockMvcBuilder> {
265+
}
257266

258-
MockServerBuilder<M> configureServer(Consumer<M> consumer);
259267

268+
interface WebAppContextSetupBuilder extends MockMvcSetupBuilder<WebAppContextSetupBuilder, DefaultMockMvcBuilder> {
260269
}
261270

262271

272+
263273
/**
264274
* Specification for providing the URI of a request.
265275
*

spring-test/src/test/java/org/springframework/test/web/servlet/client/JsonPathAssertionTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
class JsonPathAssertionTests {
4949

5050
private final RestTestClient client =
51-
RestTestClient.standaloneSetup(new MusicController())
51+
RestTestClient.bindToController(new MusicController())
5252
.configureServer(builder ->
5353
builder.alwaysExpect(status().isOk())
5454
.alwaysExpect(content().contentType(MediaType.APPLICATION_JSON))

0 commit comments

Comments
 (0)