diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java index d88acf31f2d..6c681e54799 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java @@ -28,7 +28,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.stream.Collectors; import javax.servlet.ServletContext; import javax.servlet.ServletRequest; @@ -101,9 +100,6 @@ public class MockHttpServletRequestBuilder @Nullable private Boolean secure; - @Nullable - private String queryString = ""; - @Nullable private Principal principal; @@ -123,6 +119,8 @@ public class MockHttpServletRequestBuilder private final MultiValueMap parameters = new LinkedMultiValueMap<>(); + private final MultiValueMap queryParams = new LinkedMultiValueMap<>(); + private final List cookies = new ArrayList<>(); private final List locales = new ArrayList<>(); @@ -252,6 +250,10 @@ public class MockHttpServletRequestBuilder /** * Set the request body. + *

If content is provided and {@link #contentType(MediaType)} is set to + * {@code application/x-www-form-urlencoded}, the content will be parsed + * and used to populate the {@link #param(String, String...) request + * parameters} map. * @param content the body content */ public MockHttpServletRequestBuilder content(byte[] content) { @@ -261,6 +263,10 @@ public class MockHttpServletRequestBuilder /** * Set the request body as a UTF-8 String. + *

If content is provided and {@link #contentType(MediaType)} is set to + * {@code application/x-www-form-urlencoded}, the content will be parsed + * and used to populate the {@link #param(String, String...) request + * parameters} map. * @param content the body content */ public MockHttpServletRequestBuilder content(String content) { @@ -270,6 +276,10 @@ public class MockHttpServletRequestBuilder /** * Set the 'Content-Type' header of the request. + *

If content is provided and {@code contentType} is set to + * {@code application/x-www-form-urlencoded}, the content will be parsed + * and used to populate the {@link #param(String, String...) request + * parameters} map. * @param contentType the content type */ public MockHttpServletRequestBuilder contentType(MediaType contentType) { @@ -332,8 +342,18 @@ public class MockHttpServletRequestBuilder } /** - * Add a request parameter to the {@link MockHttpServletRequest}. - *

If called more than once, new values get added to existing ones. + * Add a request parameter to {@link MockHttpServletRequest#getParameterMap()}. + *

In the Servlet API, a request parameter may be parsed from the query + * string and/or from the body of an {@code application/x-www-form-urlencoded} + * request. This method simply adds to the request parameter map. You may + * also use add Servlet request parameters by specifying the query or form + * data through one of the following: + *

    + *
  • Supply a URL with a query to {@link MockMvcRequestBuilders}. + *
  • Add query params via {@link #queryParam} or {@link #queryParams}. + *
  • Provide {@link #content} with {@link #contentType} + * {@code application/x-www-form-urlencoded}. + *
* @param name the parameter name * @param values one or more values */ @@ -343,9 +363,7 @@ public class MockHttpServletRequestBuilder } /** - * Add a map of request parameters to the {@link MockHttpServletRequest}, - * for example when testing a form submission. - *

If called more than once, new values get added to existing ones. + * Variant of {@link #param(String, String...)} with a {@link MultiValueMap}. * @param params the parameters to add * @since 4.2.4 */ @@ -359,36 +377,29 @@ public class MockHttpServletRequestBuilder } /** - * Add a query parameter to the {@link MockHttpServletRequest}. - *

If called more than once, new values get added to existing ones. + * Append to the query string and also add to the + * {@link #param(String, String...) request parameters} map. The parameter + * name and value are encoded when they are added to the query string. * @param name the parameter name * @param values one or more values + * @since 5.2.2 */ public MockHttpServletRequestBuilder queryParam(String name, String... values) { param(name, values); - String builder = Arrays.stream(values).map(value -> UriUtils.encode(name, StandardCharsets.UTF_8) + - ((value != null) ? ("=" + UriUtils.encode(value, StandardCharsets.UTF_8)) : "") + "&" - ).collect(Collectors.joining()); - queryString += builder; + this.queryParams.addAll(name, Arrays.asList(values)); return this; } /** - * Add a map of query parameters to the {@link MockHttpServletRequest}, - * for example when testing a form submission. - *

If called more than once, new values get added to existing ones. + * Append to the query string and also add to the + * {@link #params(MultiValueMap)} request parameters} map. The parameter + * name and value are encoded when they are added to the query string. * @param params the parameters to add - * @since 4.2.4 + * @since 5.2.2 */ public MockHttpServletRequestBuilder queryParams(MultiValueMap params) { params(params); - StringBuilder builder = new StringBuilder(); - params.forEach((key, values) -> values.forEach(value -> { - builder.append(UriUtils.encode(key, StandardCharsets.UTF_8)) - .append(((value != null) ? ("=" + UriUtils.encode(value, StandardCharsets.UTF_8)) : "")) - .append("&"); - })); - queryString += builder.toString(); + this.queryParams.addAll(params); return this; } @@ -581,6 +592,12 @@ public class MockHttpServletRequestBuilder this.parameters.put(paramName, entry.getValue()); } } + for (Map.Entry> entry : parentBuilder.queryParams.entrySet()) { + String paramName = entry.getKey(); + if (!this.queryParams.containsKey(paramName)) { + this.queryParams.put(paramName, entry.getValue()); + } + } for (Cookie cookie : parentBuilder.cookies) { if (!containsCookie(cookie)) { this.cookies.add(cookie); @@ -670,28 +687,22 @@ public class MockHttpServletRequestBuilder } }); - if (this.url.getRawQuery() != null) { - request.setQueryString(this.url.getRawQuery()); + String query = this.url.getRawQuery(); + if (!this.queryParams.isEmpty()) { + String s = UriComponentsBuilder.newInstance().queryParams(this.queryParams).build().encode().getQuery(); + query = StringUtils.isEmpty(query) ? s : query + "&" + s; + } + if (query != null) { + request.setQueryString(query); } addRequestParams(request, UriComponentsBuilder.fromUri(this.url).build().getQueryParams()); + this.parameters.forEach((name, values) -> { for (String value : values) { request.addParameter(name, value); } }); - StringBuilder queryBuilder = new StringBuilder(); - if (request.getQueryString() != null) { - queryBuilder.append(request.getQueryString()); - } - if (this.queryString != null && !"".equals(this.queryString)) { - if (queryBuilder.length() > 0) { - queryBuilder.append("&"); - } - queryBuilder.append(this.queryString, 0, this.queryString.length() - 1); - request.setQueryString(queryBuilder.toString()); - } - if (this.content != null && this.content.length > 0) { String requestContentType = request.getContentType(); if (requestContentType != null) { diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java index 34c29ce4b3c..405bfdb46d7 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java @@ -236,7 +236,7 @@ public class MockHttpServletRequestBuilderTests { } @Test - public void requestParameterToQuery() { + public void queryParameter() { this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/"); this.builder.queryParam("foo", "bar"); this.builder.queryParam("foo", "baz"); @@ -248,7 +248,7 @@ public class MockHttpServletRequestBuilderTests { } @Test - public void requestParameterMapToQuery() { + public void queryParameterMap() { this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/"); MultiValueMap queryParams = new LinkedMultiValueMap<>(); List values = new ArrayList<>(); @@ -264,7 +264,7 @@ public class MockHttpServletRequestBuilderTests { } @Test - public void requestParameterToQueryList() { + public void queryParameterList() { this.builder = new MockHttpServletRequestBuilder(HttpMethod.GET, "/"); this.builder.queryParam("foo[0]", "bar"); this.builder.queryParam("foo[1]", "baz");