diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClient.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClient.java index 1289c8af449..45177fb058c 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClient.java @@ -226,6 +226,12 @@ class DefaultRestTestClient implements RestTestClient { return this; } + @Override + public RequestBodySpec apiVersion(Object version) { + this.requestHeadersUriSpec.apiVersion(version); + return this; + } + @Override public RequestHeadersSpec body(Object body) { this.requestHeadersUriSpec.body(body); @@ -235,8 +241,8 @@ class DefaultRestTestClient implements RestTestClient { @Override public ResponseSpec exchange() { return new DefaultResponseSpec( - this.requestHeadersUriSpec.exchangeForRequiredValue((request, response) -> - new ExchangeResult(request, response, this.uriTemplate), false)); + this.requestHeadersUriSpec.exchangeForRequiredValue( + (request, response) -> new ExchangeResult(request, response, this.uriTemplate), false)); } } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClientBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClientBuilder.java index 748deb67aaf..14b37ff0442 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClientBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClientBuilder.java @@ -31,6 +31,7 @@ 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.ApiVersionInserter; import org.springframework.web.client.RestClient; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.function.RouterFunction; @@ -94,6 +95,18 @@ class DefaultRestTestClientBuilder> implemen return self(); } + @Override + public T defaultApiVersion(Object version) { + this.restClientBuilder.defaultApiVersion(version); + return self(); + } + + @Override + public T apiVersionInserter(ApiVersionInserter apiVersionInserter) { + this.restClientBuilder.apiVersionInserter(apiVersionInserter); + return self(); + } + @SuppressWarnings("unchecked") protected T self() { return (T) this; diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/RestTestClient.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/RestTestClient.java index 2394060227a..836979aa25a 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/RestTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/RestTestClient.java @@ -41,6 +41,8 @@ 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.ApiVersionFormatter; +import org.springframework.web.client.ApiVersionInserter; import org.springframework.web.client.RestClient; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.function.RouterFunction; @@ -241,6 +243,24 @@ public interface RestTestClient { */ T defaultCookies(Consumer> cookiesConsumer); + /** + * Global option to specify an API version to add to every request, + * if not already set. + * @param version the version to use + * @return this builder + * @since 7.0 + */ + T defaultApiVersion(Object version); + + /** + * Configure an {@link ApiVersionInserter} to abstract how an API version + * specified via {@link RequestHeadersSpec#apiVersion(Object)} + * is inserted into the request. + * @param apiVersionInserter the inserter to use + * @since 7.0 + */ + T apiVersionInserter(ApiVersionInserter apiVersionInserter); + /** * Build the {@link RestTestClient} instance. */ @@ -403,6 +423,17 @@ public interface RestTestClient { */ S headers(Consumer headersConsumer); + /** + * Set an API version for the request. The version is inserted into the + * request by the {@linkplain Builder#apiVersionInserter(ApiVersionInserter) + * configured} {@code ApiVersionInserter}. + * @param version the API version of the request; this can be a String or + * some Object that can be formatted by the inserter — for example, + * through an {@link ApiVersionFormatter} + * @since 7.0 + */ + S apiVersion(Object version); + /** * Set the attribute with the given name to the given value. * @param name the name of the attribute to add diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/client/samples/ApiVersionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/client/samples/ApiVersionTests.java new file mode 100644 index 00000000000..4ea6e81a1f0 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/client/samples/ApiVersionTests.java @@ -0,0 +1,110 @@ +/* + * 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.samples; + + +import java.util.List; +import java.util.Map; + +import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.Test; + +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.client.RestTestClient; +import org.springframework.web.accept.ApiVersionResolver; +import org.springframework.web.accept.DefaultApiVersionStrategy; +import org.springframework.web.accept.PathApiVersionResolver; +import org.springframework.web.accept.SemanticApiVersionParser; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.ApiVersionInserter; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * {@link RestTestClient} tests for sending API versions. + * + * @author Rossen Stoyanchev + */ +public class ApiVersionTests { + + @Test + void header() { + String header = "X-API-Version"; + + Map result = performRequest( + request -> request.getHeader(header), ApiVersionInserter.useHeader(header)); + + assertThat(result.get(header)).isEqualTo("1.2"); + } + + @Test + void queryParam() { + String param = "api-version"; + + Map result = performRequest( + request -> request.getParameter(param), ApiVersionInserter.useQueryParam(param)); + + assertThat(result.get("query")).isEqualTo(param + "=1.2"); + } + + @Test + void pathSegment() { + Map result = performRequest( + new PathApiVersionResolver(0), ApiVersionInserter.usePathSegment(0)); + + assertThat(result.get("path")).isEqualTo("/1.2/path"); + } + + @SuppressWarnings("unchecked") + private Map performRequest( + ApiVersionResolver versionResolver, ApiVersionInserter inserter) { + + DefaultApiVersionStrategy versionStrategy = new DefaultApiVersionStrategy( + List.of(versionResolver), new SemanticApiVersionParser(), + true, null, true, null); + + RestTestClient client = RestTestClient.bindToController(new TestController()) + .configureServer(mockMvcBuilder -> mockMvcBuilder.setApiVersionStrategy(versionStrategy)) + .baseUrl("/path") + .apiVersionInserter(inserter) + .build(); + + return client.get() + .accept(MediaType.APPLICATION_JSON) + .apiVersion(1.2) + .exchange() + .returnResult(Map.class) + .getResponseBody(); + } + + + @RestController + static class TestController { + + private static final String HEADER = "X-API-Version"; + + @GetMapping(path = "/**", version = "1.2") + Map handle(HttpServletRequest request) { + String query = request.getQueryString(); + String versionHeader = request.getHeader(HEADER); + return Map.of("path", request.getRequestURI(), + "query", (query != null ? query : ""), + HEADER, (versionHeader != null ? versionHeader : "")); + } + } +}