diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClient.java
index bab99a0a41..7967512b3f 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClient.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcWebTestClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 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.
@@ -38,6 +38,7 @@ import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.test.web.servlet.setup.RouterFunctionMockMvcBuilder;
import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder;
import org.springframework.validation.Validator;
+import org.springframework.web.accept.ApiVersionStrategy;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
@@ -284,6 +285,14 @@ public interface MockMvcWebTestClient {
*/
ControllerSpec conversionService(FormattingConversionService conversionService);
+ /**
+ * Set the {@link ApiVersionStrategy} to use when mapping requests.
+ *
This is delegated to
+ * {@link StandaloneMockMvcBuilder#setApiVersionStrategy(ApiVersionStrategy)}.
+ * @since 7.0
+ */
+ ControllerSpec apiVersionStrategy(ApiVersionStrategy versionStrategy);
+
/**
* Add global interceptors.
*
This is delegated to
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/StandaloneMockMvcSpec.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/StandaloneMockMvcSpec.java
index b4a2e9c0e1..d0f6712f11 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/StandaloneMockMvcSpec.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/StandaloneMockMvcSpec.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 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.
@@ -26,6 +26,7 @@ import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder;
import org.springframework.validation.Validator;
+import org.springframework.web.accept.ApiVersionStrategy;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
@@ -79,6 +80,12 @@ class StandaloneMockMvcSpec extends AbstractMockMvcServerSpec locales = new ArrayList<>();
+ private @Nullable Object version;
+
+ private @Nullable ApiVersionInserter versionInserter;
+
private final Map requestAttributes = new LinkedHashMap<>();
private final Map sessionAttributes = new LinkedHashMap<>();
@@ -469,6 +476,34 @@ public abstract class AbstractMockHttpServletRequestBuilder entry : parentBuilder.requestAttributes.entrySet()) {
String attributeName = entry.getKey();
if (!this.requestAttributes.containsKey(attributeName)) {
@@ -700,7 +743,15 @@ public abstract class AbstractMockHttpServletRequestBuilder values.forEach(value -> this.headers.add(name, value)));
+ }
+
this.headers.forEach((name, values) -> {
for (Object value : values) {
request.addHeader(name, value);
@@ -753,7 +811,7 @@ public abstract class AbstractMockHttpServletRequestBuilder {
for (String value : values) {
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java
index 7667f652d4..2189ad5dd6 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java
@@ -36,10 +36,12 @@ import org.springframework.test.web.servlet.MockMvcBuilderSupport;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.test.web.servlet.ResultMatcher;
+import org.springframework.test.web.servlet.request.AbstractMockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.ConfigurableSmartRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.util.Assert;
+import org.springframework.web.client.ApiVersionInserter;
import org.springframework.web.context.WebApplicationContext;
/**
@@ -62,6 +64,8 @@ public abstract class AbstractMockMvcBuilder
private final List filters = new ArrayList<>();
+ private @Nullable ApiVersionInserter apiVersionInserter;
+
private @Nullable RequestBuilder defaultRequestBuilder;
private @Nullable Charset defaultResponseCharacterEncoding;
@@ -106,6 +110,12 @@ public abstract class AbstractMockMvcBuilder
return self();
}
+ @Override
+ public T apiVersionInserter(ApiVersionInserter versionInserter) {
+ this.apiVersionInserter = versionInserter;
+ return self();
+ }
+
@Override
public final T defaultRequest(RequestBuilder requestBuilder) {
this.defaultRequestBuilder = requestBuilder;
@@ -194,6 +204,15 @@ public abstract class AbstractMockMvcBuilder
}
}
+ if (this.apiVersionInserter != null) {
+ if (this.defaultRequestBuilder == null) {
+ this.defaultRequestBuilder = MockMvcRequestBuilders.get("/");
+ }
+ if (this.defaultRequestBuilder instanceof AbstractMockHttpServletRequestBuilder> srb) {
+ srb.apiVersionInserter(this.apiVersionInserter);
+ }
+ }
+
return super.createMockMvc(filterArray, mockServletConfig, wac, this.defaultRequestBuilder,
this.defaultResponseCharacterEncoding, this.globalResultMatchers, this.globalResultHandlers,
this.dispatcherServletCustomizers);
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/ConfigurableMockMvcBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/ConfigurableMockMvcBuilder.java
index 109280cd53..7afd093c88 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/ConfigurableMockMvcBuilder.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/ConfigurableMockMvcBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2025 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.
@@ -30,6 +30,7 @@ import org.springframework.test.web.servlet.MockMvcBuilder;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.test.web.servlet.ResultMatcher;
+import org.springframework.web.client.ApiVersionInserter;
/**
* Defines common methods for building a {@code MockMvc}.
@@ -76,6 +77,14 @@ public interface ConfigurableMockMvcBuilder initParams,
EnumSet dispatcherTypes, String... urlPatterns);
+ /**
+ * Set the {@link ApiVersionInserter} to use to apply to versions specified via
+ * {@link org.springframework.test.web.servlet.request.AbstractMockHttpServletRequestBuilder#apiVersion(Object)}.
+ * @param versionInserter the inserter to use
+ * @since 7.0
+ */
+ T apiVersionInserter(ApiVersionInserter versionInserter);
+
/**
* Define default request properties that should be merged into all
* performed requests. In effect this provides a mechanism for defining
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.java
index 4f0ec932a9..2759335aed 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 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.
@@ -42,6 +42,7 @@ import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
import org.springframework.util.StringValueResolver;
import org.springframework.validation.Validator;
+import org.springframework.web.accept.ApiVersionStrategy;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationObjectSupport;
@@ -108,6 +109,8 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder handlerExceptionResolvers;
private @Nullable Long asyncRequestTimeout;
@@ -189,6 +192,15 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder builder) {
return builder.buildRequest(this.servletContext);
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ApiVersionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ApiVersionTests.java
new file mode 100644
index 0000000000..2472ed3154
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ApiVersionTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2025 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.samples.standalone;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Controller;
+import org.springframework.test.web.Person;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.web.accept.DefaultApiVersionStrategy;
+import org.springframework.web.accept.SemanticApiVersionParser;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.client.ApiVersionInserter;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
+
+/**
+ * Tests demonstrating the use of API version.
+ * @author Rossen Stoyanchev
+ */
+public class ApiVersionTests {
+
+ @Test
+ public void queryParameter() throws Exception {
+
+ String header = "API-Version";
+
+ DefaultApiVersionStrategy versionStrategy = new DefaultApiVersionStrategy(
+ List.of(request -> request.getHeader(header)),
+ new SemanticApiVersionParser(),
+ true, null);
+
+ MockMvc mockMvc = standaloneSetup(new PersonController())
+ .setApiVersionStrategy(versionStrategy)
+ .apiVersionInserter(ApiVersionInserter.useHeader(header))
+ .build();
+
+ mockMvc.perform(get("/search?name=George").accept(MediaType.APPLICATION_JSON).apiVersion(1.1))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType("application/json"))
+ .andExpect(jsonPath("$.name").value("George"));
+ }
+
+
+ @Controller
+ private static class PersonController {
+
+ @RequestMapping(path="/search", version = "1.1")
+ @ResponseBody
+ public Person get(@RequestParam String name) {
+ return new Person(name);
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilderTests.java
index 0d120a5832..9ef7f287a1 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilderTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilderTests.java
@@ -36,6 +36,7 @@ import org.springframework.http.support.JacksonHandlerInstantiator;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.web.accept.ApiVersionStrategy;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
@@ -74,6 +75,18 @@ class StandaloneMockMvcBuilderTests {
assertThat(((HandlerMethod) chain.getHandler()).getMethod().getName()).isEqualTo("handleWithPlaceholders");
}
+ @Test
+ void apiVersionStrategySet() {
+ ApiVersionStrategy versionStrategy = mock(ApiVersionStrategy.class);
+
+ TestStandaloneMockMvcBuilder builder = new TestStandaloneMockMvcBuilder();
+ builder.setApiVersionStrategy(versionStrategy);
+ builder.build();
+
+ assertThat(builder.wac.getBean(RequestMappingHandlerMapping.class).getApiVersionStrategy())
+ .isSameAs(versionStrategy);
+ }
+
@Test // SPR-12553
void applicationContextAttribute() {
TestStandaloneMockMvcBuilder builder = new TestStandaloneMockMvcBuilder(new PlaceholderController());