Skip to content

Commit 6cc1310

Browse files
committed
Add API versioning to RestTestClient
See gh-34428
1 parent 862ffee commit 6cc1310

File tree

4 files changed

+162
-2
lines changed

4 files changed

+162
-2
lines changed

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,12 @@ public RequestBodySpec attributes(Consumer<Map<String, Object>> attributesConsum
226226
return this;
227227
}
228228

229+
@Override
230+
public RequestBodySpec apiVersion(Object version) {
231+
this.requestHeadersUriSpec.apiVersion(version);
232+
return this;
233+
}
234+
229235
@Override
230236
public RequestHeadersSpec<?> body(Object body) {
231237
this.requestHeadersUriSpec.body(body);
@@ -235,8 +241,8 @@ public RequestHeadersSpec<?> body(Object body) {
235241
@Override
236242
public ResponseSpec exchange() {
237243
return new DefaultResponseSpec(
238-
this.requestHeadersUriSpec.exchangeForRequiredValue((request, response) ->
239-
new ExchangeResult(request, response, this.uriTemplate), false));
244+
this.requestHeadersUriSpec.exchangeForRequiredValue(
245+
(request, response) -> new ExchangeResult(request, response, this.uriTemplate), false));
240246
}
241247
}
242248

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.test.web.servlet.setup.RouterFunctionMockMvcBuilder;
3232
import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder;
3333
import org.springframework.util.MultiValueMap;
34+
import org.springframework.web.client.ApiVersionInserter;
3435
import org.springframework.web.client.RestClient;
3536
import org.springframework.web.context.WebApplicationContext;
3637
import org.springframework.web.servlet.function.RouterFunction;
@@ -94,6 +95,18 @@ public <T extends B> T defaultCookies(Consumer<MultiValueMap<String, String>> co
9495
return self();
9596
}
9697

98+
@Override
99+
public <T extends B> T defaultApiVersion(Object version) {
100+
this.restClientBuilder.defaultApiVersion(version);
101+
return self();
102+
}
103+
104+
@Override
105+
public <T extends B> T apiVersionInserter(ApiVersionInserter apiVersionInserter) {
106+
this.restClientBuilder.apiVersionInserter(apiVersionInserter);
107+
return self();
108+
}
109+
97110
@SuppressWarnings("unchecked")
98111
protected <T extends B> T self() {
99112
return (T) this;

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
import org.springframework.test.web.servlet.setup.RouterFunctionMockMvcBuilder;
4242
import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder;
4343
import org.springframework.util.MultiValueMap;
44+
import org.springframework.web.client.ApiVersionFormatter;
45+
import org.springframework.web.client.ApiVersionInserter;
4446
import org.springframework.web.client.RestClient;
4547
import org.springframework.web.context.WebApplicationContext;
4648
import org.springframework.web.servlet.function.RouterFunction;
@@ -241,6 +243,24 @@ interface Builder<B extends Builder<B>> {
241243
*/
242244
<T extends B> T defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
243245

246+
/**
247+
* Global option to specify an API version to add to every request,
248+
* if not already set.
249+
* @param version the version to use
250+
* @return this builder
251+
* @since 7.0
252+
*/
253+
<T extends B> T defaultApiVersion(Object version);
254+
255+
/**
256+
* Configure an {@link ApiVersionInserter} to abstract how an API version
257+
* specified via {@link RequestHeadersSpec#apiVersion(Object)}
258+
* is inserted into the request.
259+
* @param apiVersionInserter the inserter to use
260+
* @since 7.0
261+
*/
262+
<T extends B> T apiVersionInserter(ApiVersionInserter apiVersionInserter);
263+
244264
/**
245265
* Build the {@link RestTestClient} instance.
246266
*/
@@ -403,6 +423,17 @@ interface RequestHeadersSpec<S extends RequestHeadersSpec<S>> {
403423
*/
404424
S headers(Consumer<HttpHeaders> headersConsumer);
405425

426+
/**
427+
* Set an API version for the request. The version is inserted into the
428+
* request by the {@linkplain Builder#apiVersionInserter(ApiVersionInserter)
429+
* configured} {@code ApiVersionInserter}.
430+
* @param version the API version of the request; this can be a String or
431+
* some Object that can be formatted by the inserter &mdash; for example,
432+
* through an {@link ApiVersionFormatter}
433+
* @since 7.0
434+
*/
435+
S apiVersion(Object version);
436+
406437
/**
407438
* Set the attribute with the given name to the given value.
408439
* @param name the name of the attribute to add
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright 2002-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.web.servlet.client.samples;
18+
19+
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
import jakarta.servlet.http.HttpServletRequest;
24+
import org.junit.jupiter.api.Test;
25+
26+
import org.springframework.http.MediaType;
27+
import org.springframework.test.web.servlet.client.RestTestClient;
28+
import org.springframework.web.accept.ApiVersionResolver;
29+
import org.springframework.web.accept.DefaultApiVersionStrategy;
30+
import org.springframework.web.accept.PathApiVersionResolver;
31+
import org.springframework.web.accept.SemanticApiVersionParser;
32+
import org.springframework.web.bind.annotation.GetMapping;
33+
import org.springframework.web.bind.annotation.RestController;
34+
import org.springframework.web.client.ApiVersionInserter;
35+
36+
import static org.assertj.core.api.Assertions.assertThat;
37+
38+
/**
39+
* {@link RestTestClient} tests for sending API versions.
40+
*
41+
* @author Rossen Stoyanchev
42+
*/
43+
public class ApiVersionTests {
44+
45+
@Test
46+
void header() {
47+
String header = "X-API-Version";
48+
49+
Map<String, String> result = performRequest(
50+
request -> request.getHeader(header), ApiVersionInserter.useHeader(header));
51+
52+
assertThat(result.get(header)).isEqualTo("1.2");
53+
}
54+
55+
@Test
56+
void queryParam() {
57+
String param = "api-version";
58+
59+
Map<String, String> result = performRequest(
60+
request -> request.getParameter(param), ApiVersionInserter.useQueryParam(param));
61+
62+
assertThat(result.get("query")).isEqualTo(param + "=1.2");
63+
}
64+
65+
@Test
66+
void pathSegment() {
67+
Map<String, String> result = performRequest(
68+
new PathApiVersionResolver(0), ApiVersionInserter.usePathSegment(0));
69+
70+
assertThat(result.get("path")).isEqualTo("/1.2/path");
71+
}
72+
73+
@SuppressWarnings("unchecked")
74+
private Map<String, String> performRequest(
75+
ApiVersionResolver versionResolver, ApiVersionInserter inserter) {
76+
77+
DefaultApiVersionStrategy versionStrategy = new DefaultApiVersionStrategy(
78+
List.of(versionResolver), new SemanticApiVersionParser(),
79+
true, null, true, null);
80+
81+
RestTestClient client = RestTestClient.bindToController(new TestController())
82+
.configureServer(mockMvcBuilder -> mockMvcBuilder.setApiVersionStrategy(versionStrategy))
83+
.baseUrl("/path")
84+
.apiVersionInserter(inserter)
85+
.build();
86+
87+
return client.get()
88+
.accept(MediaType.APPLICATION_JSON)
89+
.apiVersion(1.2)
90+
.exchange()
91+
.returnResult(Map.class)
92+
.getResponseBody();
93+
}
94+
95+
96+
@RestController
97+
static class TestController {
98+
99+
private static final String HEADER = "X-API-Version";
100+
101+
@GetMapping(path = "/**", version = "1.2")
102+
Map<String, String> handle(HttpServletRequest request) {
103+
String query = request.getQueryString();
104+
String versionHeader = request.getHeader(HEADER);
105+
return Map.of("path", request.getRequestURI(),
106+
"query", (query != null ? query : ""),
107+
HEADER, (versionHeader != null ? versionHeader : ""));
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)