Restore source and binary compatibility in MockMvc
Previous to this commit, MockHttpServletRequestBuilder was not binary compatible as its methods had moved to a parent class with a generic argument on the return type. MockMultipartHttpServletRequestBuilder was also not source compatible as it no longed extended from MockHttpServletRequestBuilder. Both these changes were introduced to allow the AssertJ support to expose builders that implement an extra AssertJ interface, without copying the features the builders provide. This commit restore compatibility. For MockHttpServletRequestBuilder we simply override all methods that returns the generic type into the resolved type. MockMultipartHttpServletRequestBuilder is more involved. Because we need to extend from MockHttpServletRequestBuilder, we have no other choice than duplicating the code. For now, the abstract builder for multipart is only used by the AssertJ support, but we can revisit this again in a major release. Closes gh-33229
This commit is contained in:
@@ -16,9 +16,22 @@
|
||||
|
||||
package org.springframework.test.web.servlet.request;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.Principal;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.http.Cookie;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpSession;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Default builder for {@link MockHttpServletRequest} required as input to
|
||||
@@ -53,4 +66,177 @@ public class MockHttpServletRequestBuilder
|
||||
super(httpMethod);
|
||||
}
|
||||
|
||||
|
||||
// Override to keep binary compatibility.
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder uri(URI uri) {
|
||||
return super.uri(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder uri(String uriTemplate, Object... uriVariables) {
|
||||
return super.uri(uriTemplate, uriVariables);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder contextPath(String contextPath) {
|
||||
return super.contextPath(contextPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder servletPath(String servletPath) {
|
||||
return super.servletPath(servletPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder pathInfo(@Nullable String pathInfo) {
|
||||
return super.pathInfo(pathInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder secure(boolean secure) {
|
||||
return super.secure(secure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder characterEncoding(Charset encoding) {
|
||||
return super.characterEncoding(encoding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder characterEncoding(String encoding) {
|
||||
return super.characterEncoding(encoding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder content(byte[] content) {
|
||||
return super.content(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder content(String content) {
|
||||
return super.content(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder contentType(MediaType contentType) {
|
||||
return super.contentType(contentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder contentType(String contentType) {
|
||||
return super.contentType(contentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder accept(MediaType... mediaTypes) {
|
||||
return super.accept(mediaTypes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder accept(String... mediaTypes) {
|
||||
return super.accept(mediaTypes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder header(String name, Object... values) {
|
||||
return super.header(name, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder headers(HttpHeaders httpHeaders) {
|
||||
return super.headers(httpHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder param(String name, String... values) {
|
||||
return super.param(name, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder params(MultiValueMap<String, String> params) {
|
||||
return super.params(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder queryParam(String name, String... values) {
|
||||
return super.queryParam(name, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder queryParams(MultiValueMap<String, String> params) {
|
||||
return super.queryParams(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder formField(String name, String... values) {
|
||||
return super.formField(name, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder formFields(MultiValueMap<String, String> formFields) {
|
||||
return super.formFields(formFields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder cookie(Cookie... cookies) {
|
||||
return super.cookie(cookies);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder locale(Locale... locales) {
|
||||
return super.locale(locales);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder locale(@Nullable Locale locale) {
|
||||
return super.locale(locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder requestAttr(String name, Object value) {
|
||||
return super.requestAttr(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder sessionAttr(String name, Object value) {
|
||||
return super.sessionAttr(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder sessionAttrs(Map<String, Object> sessionAttributes) {
|
||||
return super.sessionAttrs(sessionAttributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder flashAttr(String name, Object value) {
|
||||
return super.flashAttr(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder flashAttrs(Map<String, Object> flashAttributes) {
|
||||
return super.flashAttrs(flashAttributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder session(MockHttpSession session) {
|
||||
return super.session(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder principal(Principal principal) {
|
||||
return super.principal(principal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder remoteAddress(String remoteAddress) {
|
||||
return super.remoteAddress(remoteAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequestBuilder with(RequestPostProcessor postProcessor) {
|
||||
return super.with(postProcessor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,9 +16,28 @@
|
||||
|
||||
package org.springframework.test.web.servlet.request;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.http.Part;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.mock.web.MockMultipartHttpServletRequest;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Default builder for {@link MockMultipartHttpServletRequest}.
|
||||
@@ -28,8 +47,12 @@ import org.springframework.mock.web.MockMultipartHttpServletRequest;
|
||||
* @author Stephane Nicoll
|
||||
* @since 3.2
|
||||
*/
|
||||
public class MockMultipartHttpServletRequestBuilder
|
||||
extends AbstractMockMultipartHttpServletRequestBuilder<MockMultipartHttpServletRequestBuilder> {
|
||||
public class MockMultipartHttpServletRequestBuilder extends MockHttpServletRequestBuilder {
|
||||
|
||||
private final List<MockMultipartFile> files = new ArrayList<>();
|
||||
|
||||
private final MultiValueMap<String, Part> parts = new LinkedMultiValueMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* Package-private constructor. Use static factory methods in
|
||||
@@ -53,4 +76,100 @@ public class MockMultipartHttpServletRequestBuilder
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a new {@link MockMultipartFile} with the given content.
|
||||
* @param name the name of the file
|
||||
* @param content the content of the file
|
||||
*/
|
||||
public MockMultipartHttpServletRequestBuilder file(String name, byte[] content) {
|
||||
this.files.add(new MockMultipartFile(name, content));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given {@link MockMultipartFile}.
|
||||
* @param file the multipart file
|
||||
*/
|
||||
public MockMultipartHttpServletRequestBuilder file(MockMultipartFile file) {
|
||||
this.files.add(file);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add {@link Part} components to the request.
|
||||
* @param parts one or more parts to add
|
||||
* @since 5.0
|
||||
*/
|
||||
public MockMultipartHttpServletRequestBuilder part(Part... parts) {
|
||||
Assert.notEmpty(parts, "'parts' must not be empty");
|
||||
for (Part part : parts) {
|
||||
this.parts.add(part.getName(), part);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object merge(@Nullable Object parent) {
|
||||
if (parent == null) {
|
||||
return this;
|
||||
}
|
||||
if (parent instanceof MockHttpServletRequestBuilder) {
|
||||
super.merge(parent);
|
||||
if (parent instanceof MockMultipartHttpServletRequestBuilder parentBuilder) {
|
||||
this.files.addAll(parentBuilder.files);
|
||||
parentBuilder.parts.keySet().forEach(name ->
|
||||
this.parts.putIfAbsent(name, parentBuilder.parts.get(name)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Cannot merge with [" + parent.getClass().getName() + "]");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link MockMultipartHttpServletRequest} based on the
|
||||
* supplied {@code ServletContext} and the {@code MockMultipartFiles}
|
||||
* added to this builder.
|
||||
*/
|
||||
@Override
|
||||
protected final MockHttpServletRequest createServletRequest(ServletContext servletContext) {
|
||||
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest(servletContext);
|
||||
Charset defaultCharset = (request.getCharacterEncoding() != null ?
|
||||
Charset.forName(request.getCharacterEncoding()) : StandardCharsets.UTF_8);
|
||||
|
||||
this.files.forEach(request::addFile);
|
||||
this.parts.values().stream().flatMap(Collection::stream).forEach(part -> {
|
||||
request.addPart(part);
|
||||
try {
|
||||
String name = part.getName();
|
||||
String filename = part.getSubmittedFileName();
|
||||
InputStream is = part.getInputStream();
|
||||
if (filename != null) {
|
||||
request.addFile(new MockMultipartFile(name, filename, part.getContentType(), is));
|
||||
}
|
||||
else {
|
||||
InputStreamReader reader = new InputStreamReader(is, getCharsetOrDefault(part, defaultCharset));
|
||||
String value = FileCopyUtils.copyToString(reader);
|
||||
request.addParameter(part.getName(), value);
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Failed to read content for part " + part.getName(), ex);
|
||||
}
|
||||
});
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private Charset getCharsetOrDefault(Part part, Charset defaultCharset) {
|
||||
if (part.getContentType() != null) {
|
||||
MediaType mediaType = MediaType.parseMediaType(part.getContentType());
|
||||
if (mediaType.getCharset() != null) {
|
||||
return mediaType.getCharset();
|
||||
}
|
||||
}
|
||||
return defaultCharset;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -214,7 +214,9 @@ public abstract class MockMvcRequestBuilders {
|
||||
* @since 5.0
|
||||
*/
|
||||
public static MockMultipartHttpServletRequestBuilder multipart(String uriTemplate, Object... uriVariables) {
|
||||
return new MockMultipartHttpServletRequestBuilder().uri(uriTemplate, uriVariables);
|
||||
MockMultipartHttpServletRequestBuilder builder = new MockMultipartHttpServletRequestBuilder();
|
||||
builder.uri(uriTemplate, uriVariables);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,7 +228,9 @@ public abstract class MockMvcRequestBuilders {
|
||||
* @since 5.3.22
|
||||
*/
|
||||
public static MockMultipartHttpServletRequestBuilder multipart(HttpMethod httpMethod, String uriTemplate, Object... uriVariables) {
|
||||
return new MockMultipartHttpServletRequestBuilder(httpMethod).uri(uriTemplate, uriVariables);
|
||||
MockMultipartHttpServletRequestBuilder builder = new MockMultipartHttpServletRequestBuilder(httpMethod);
|
||||
builder.uri(uriTemplate, uriVariables);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,7 +239,9 @@ public abstract class MockMvcRequestBuilders {
|
||||
* @since 5.0
|
||||
*/
|
||||
public static MockMultipartHttpServletRequestBuilder multipart(URI uri) {
|
||||
return new MockMultipartHttpServletRequestBuilder().uri(uri);
|
||||
MockMultipartHttpServletRequestBuilder builder = new MockMultipartHttpServletRequestBuilder();
|
||||
builder.uri(uri);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -246,7 +252,9 @@ public abstract class MockMvcRequestBuilders {
|
||||
* @since 5.3.21
|
||||
*/
|
||||
public static MockMultipartHttpServletRequestBuilder multipart(HttpMethod httpMethod, URI uri) {
|
||||
return new MockMultipartHttpServletRequestBuilder(httpMethod).uri(uri);
|
||||
MockMultipartHttpServletRequestBuilder builder = new MockMultipartHttpServletRequestBuilder(httpMethod);
|
||||
builder.uri(uri);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user