Skip to content

Commit 265299c

Browse files
committed
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
1 parent 5715b2a commit 265299c

File tree

4 files changed

+328
-9
lines changed

4 files changed

+328
-9
lines changed

spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,22 @@
1616

1717
package org.springframework.test.web.servlet.request;
1818

19+
import java.net.URI;
20+
import java.nio.charset.Charset;
21+
import java.security.Principal;
22+
import java.util.Locale;
23+
import java.util.Map;
24+
25+
import jakarta.servlet.http.Cookie;
26+
27+
import org.springframework.http.HttpHeaders;
1928
import org.springframework.http.HttpMethod;
29+
import org.springframework.http.MediaType;
30+
import org.springframework.lang.Nullable;
2031
import org.springframework.mock.web.MockHttpServletRequest;
32+
import org.springframework.mock.web.MockHttpSession;
2133
import org.springframework.test.web.servlet.MockMvc;
34+
import org.springframework.util.MultiValueMap;
2235

2336
/**
2437
* Default builder for {@link MockHttpServletRequest} required as input to
@@ -53,4 +66,177 @@ public class MockHttpServletRequestBuilder
5366
super(httpMethod);
5467
}
5568

69+
70+
// Override to keep binary compatibility.
71+
72+
@Override
73+
public MockHttpServletRequestBuilder uri(URI uri) {
74+
return super.uri(uri);
75+
}
76+
77+
@Override
78+
public MockHttpServletRequestBuilder uri(String uriTemplate, Object... uriVariables) {
79+
return super.uri(uriTemplate, uriVariables);
80+
}
81+
82+
@Override
83+
public MockHttpServletRequestBuilder contextPath(String contextPath) {
84+
return super.contextPath(contextPath);
85+
}
86+
87+
@Override
88+
public MockHttpServletRequestBuilder servletPath(String servletPath) {
89+
return super.servletPath(servletPath);
90+
}
91+
92+
@Override
93+
public MockHttpServletRequestBuilder pathInfo(@Nullable String pathInfo) {
94+
return super.pathInfo(pathInfo);
95+
}
96+
97+
@Override
98+
public MockHttpServletRequestBuilder secure(boolean secure) {
99+
return super.secure(secure);
100+
}
101+
102+
@Override
103+
public MockHttpServletRequestBuilder characterEncoding(Charset encoding) {
104+
return super.characterEncoding(encoding);
105+
}
106+
107+
@Override
108+
public MockHttpServletRequestBuilder characterEncoding(String encoding) {
109+
return super.characterEncoding(encoding);
110+
}
111+
112+
@Override
113+
public MockHttpServletRequestBuilder content(byte[] content) {
114+
return super.content(content);
115+
}
116+
117+
@Override
118+
public MockHttpServletRequestBuilder content(String content) {
119+
return super.content(content);
120+
}
121+
122+
@Override
123+
public MockHttpServletRequestBuilder contentType(MediaType contentType) {
124+
return super.contentType(contentType);
125+
}
126+
127+
@Override
128+
public MockHttpServletRequestBuilder contentType(String contentType) {
129+
return super.contentType(contentType);
130+
}
131+
132+
@Override
133+
public MockHttpServletRequestBuilder accept(MediaType... mediaTypes) {
134+
return super.accept(mediaTypes);
135+
}
136+
137+
@Override
138+
public MockHttpServletRequestBuilder accept(String... mediaTypes) {
139+
return super.accept(mediaTypes);
140+
}
141+
142+
@Override
143+
public MockHttpServletRequestBuilder header(String name, Object... values) {
144+
return super.header(name, values);
145+
}
146+
147+
@Override
148+
public MockHttpServletRequestBuilder headers(HttpHeaders httpHeaders) {
149+
return super.headers(httpHeaders);
150+
}
151+
152+
@Override
153+
public MockHttpServletRequestBuilder param(String name, String... values) {
154+
return super.param(name, values);
155+
}
156+
157+
@Override
158+
public MockHttpServletRequestBuilder params(MultiValueMap<String, String> params) {
159+
return super.params(params);
160+
}
161+
162+
@Override
163+
public MockHttpServletRequestBuilder queryParam(String name, String... values) {
164+
return super.queryParam(name, values);
165+
}
166+
167+
@Override
168+
public MockHttpServletRequestBuilder queryParams(MultiValueMap<String, String> params) {
169+
return super.queryParams(params);
170+
}
171+
172+
@Override
173+
public MockHttpServletRequestBuilder formField(String name, String... values) {
174+
return super.formField(name, values);
175+
}
176+
177+
@Override
178+
public MockHttpServletRequestBuilder formFields(MultiValueMap<String, String> formFields) {
179+
return super.formFields(formFields);
180+
}
181+
182+
@Override
183+
public MockHttpServletRequestBuilder cookie(Cookie... cookies) {
184+
return super.cookie(cookies);
185+
}
186+
187+
@Override
188+
public MockHttpServletRequestBuilder locale(Locale... locales) {
189+
return super.locale(locales);
190+
}
191+
192+
@Override
193+
public MockHttpServletRequestBuilder locale(@Nullable Locale locale) {
194+
return super.locale(locale);
195+
}
196+
197+
@Override
198+
public MockHttpServletRequestBuilder requestAttr(String name, Object value) {
199+
return super.requestAttr(name, value);
200+
}
201+
202+
@Override
203+
public MockHttpServletRequestBuilder sessionAttr(String name, Object value) {
204+
return super.sessionAttr(name, value);
205+
}
206+
207+
@Override
208+
public MockHttpServletRequestBuilder sessionAttrs(Map<String, Object> sessionAttributes) {
209+
return super.sessionAttrs(sessionAttributes);
210+
}
211+
212+
@Override
213+
public MockHttpServletRequestBuilder flashAttr(String name, Object value) {
214+
return super.flashAttr(name, value);
215+
}
216+
217+
@Override
218+
public MockHttpServletRequestBuilder flashAttrs(Map<String, Object> flashAttributes) {
219+
return super.flashAttrs(flashAttributes);
220+
}
221+
222+
@Override
223+
public MockHttpServletRequestBuilder session(MockHttpSession session) {
224+
return super.session(session);
225+
}
226+
227+
@Override
228+
public MockHttpServletRequestBuilder principal(Principal principal) {
229+
return super.principal(principal);
230+
}
231+
232+
@Override
233+
public MockHttpServletRequestBuilder remoteAddress(String remoteAddress) {
234+
return super.remoteAddress(remoteAddress);
235+
}
236+
237+
@Override
238+
public MockHttpServletRequestBuilder with(RequestPostProcessor postProcessor) {
239+
return super.with(postProcessor);
240+
}
241+
56242
}

spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilder.java

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,28 @@
1616

1717
package org.springframework.test.web.servlet.request;
1818

19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.io.InputStreamReader;
22+
import java.nio.charset.Charset;
23+
import java.nio.charset.StandardCharsets;
24+
import java.util.ArrayList;
25+
import java.util.Collection;
26+
import java.util.List;
27+
28+
import jakarta.servlet.ServletContext;
29+
import jakarta.servlet.http.Part;
30+
1931
import org.springframework.http.HttpMethod;
2032
import org.springframework.http.MediaType;
33+
import org.springframework.lang.Nullable;
34+
import org.springframework.mock.web.MockHttpServletRequest;
35+
import org.springframework.mock.web.MockMultipartFile;
2136
import org.springframework.mock.web.MockMultipartHttpServletRequest;
37+
import org.springframework.util.Assert;
38+
import org.springframework.util.FileCopyUtils;
39+
import org.springframework.util.LinkedMultiValueMap;
40+
import org.springframework.util.MultiValueMap;
2241

2342
/**
2443
* Default builder for {@link MockMultipartHttpServletRequest}.
@@ -28,8 +47,12 @@
2847
* @author Stephane Nicoll
2948
* @since 3.2
3049
*/
31-
public class MockMultipartHttpServletRequestBuilder
32-
extends AbstractMockMultipartHttpServletRequestBuilder<MockMultipartHttpServletRequestBuilder> {
50+
public class MockMultipartHttpServletRequestBuilder extends MockHttpServletRequestBuilder {
51+
52+
private final List<MockMultipartFile> files = new ArrayList<>();
53+
54+
private final MultiValueMap<String, Part> parts = new LinkedMultiValueMap<>();
55+
3356

3457
/**
3558
* Package-private constructor. Use static factory methods in
@@ -53,4 +76,100 @@ public class MockMultipartHttpServletRequestBuilder
5376
}
5477

5578

79+
/**
80+
* Add a new {@link MockMultipartFile} with the given content.
81+
* @param name the name of the file
82+
* @param content the content of the file
83+
*/
84+
public MockMultipartHttpServletRequestBuilder file(String name, byte[] content) {
85+
this.files.add(new MockMultipartFile(name, content));
86+
return this;
87+
}
88+
89+
/**
90+
* Add the given {@link MockMultipartFile}.
91+
* @param file the multipart file
92+
*/
93+
public MockMultipartHttpServletRequestBuilder file(MockMultipartFile file) {
94+
this.files.add(file);
95+
return this;
96+
}
97+
98+
/**
99+
* Add {@link Part} components to the request.
100+
* @param parts one or more parts to add
101+
* @since 5.0
102+
*/
103+
public MockMultipartHttpServletRequestBuilder part(Part... parts) {
104+
Assert.notEmpty(parts, "'parts' must not be empty");
105+
for (Part part : parts) {
106+
this.parts.add(part.getName(), part);
107+
}
108+
return this;
109+
}
110+
111+
@Override
112+
public Object merge(@Nullable Object parent) {
113+
if (parent == null) {
114+
return this;
115+
}
116+
if (parent instanceof MockHttpServletRequestBuilder) {
117+
super.merge(parent);
118+
if (parent instanceof MockMultipartHttpServletRequestBuilder parentBuilder) {
119+
this.files.addAll(parentBuilder.files);
120+
parentBuilder.parts.keySet().forEach(name ->
121+
this.parts.putIfAbsent(name, parentBuilder.parts.get(name)));
122+
}
123+
}
124+
else {
125+
throw new IllegalArgumentException("Cannot merge with [" + parent.getClass().getName() + "]");
126+
}
127+
return this;
128+
}
129+
130+
/**
131+
* Create a new {@link MockMultipartHttpServletRequest} based on the
132+
* supplied {@code ServletContext} and the {@code MockMultipartFiles}
133+
* added to this builder.
134+
*/
135+
@Override
136+
protected final MockHttpServletRequest createServletRequest(ServletContext servletContext) {
137+
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest(servletContext);
138+
Charset defaultCharset = (request.getCharacterEncoding() != null ?
139+
Charset.forName(request.getCharacterEncoding()) : StandardCharsets.UTF_8);
140+
141+
this.files.forEach(request::addFile);
142+
this.parts.values().stream().flatMap(Collection::stream).forEach(part -> {
143+
request.addPart(part);
144+
try {
145+
String name = part.getName();
146+
String filename = part.getSubmittedFileName();
147+
InputStream is = part.getInputStream();
148+
if (filename != null) {
149+
request.addFile(new MockMultipartFile(name, filename, part.getContentType(), is));
150+
}
151+
else {
152+
InputStreamReader reader = new InputStreamReader(is, getCharsetOrDefault(part, defaultCharset));
153+
String value = FileCopyUtils.copyToString(reader);
154+
request.addParameter(part.getName(), value);
155+
}
156+
}
157+
catch (IOException ex) {
158+
throw new IllegalStateException("Failed to read content for part " + part.getName(), ex);
159+
}
160+
});
161+
162+
return request;
163+
}
164+
165+
private Charset getCharsetOrDefault(Part part, Charset defaultCharset) {
166+
if (part.getContentType() != null) {
167+
MediaType mediaType = MediaType.parseMediaType(part.getContentType());
168+
if (mediaType.getCharset() != null) {
169+
return mediaType.getCharset();
170+
}
171+
}
172+
return defaultCharset;
173+
}
174+
56175
}

spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,9 @@ public static MockHttpServletRequestBuilder request(String httpMethod, URI uri)
214214
* @since 5.0
215215
*/
216216
public static MockMultipartHttpServletRequestBuilder multipart(String uriTemplate, Object... uriVariables) {
217-
return new MockMultipartHttpServletRequestBuilder().uri(uriTemplate, uriVariables);
217+
MockMultipartHttpServletRequestBuilder builder = new MockMultipartHttpServletRequestBuilder();
218+
builder.uri(uriTemplate, uriVariables);
219+
return builder;
218220
}
219221

220222
/**
@@ -226,7 +228,9 @@ public static MockMultipartHttpServletRequestBuilder multipart(String uriTemplat
226228
* @since 5.3.22
227229
*/
228230
public static MockMultipartHttpServletRequestBuilder multipart(HttpMethod httpMethod, String uriTemplate, Object... uriVariables) {
229-
return new MockMultipartHttpServletRequestBuilder(httpMethod).uri(uriTemplate, uriVariables);
231+
MockMultipartHttpServletRequestBuilder builder = new MockMultipartHttpServletRequestBuilder(httpMethod);
232+
builder.uri(uriTemplate, uriVariables);
233+
return builder;
230234
}
231235

232236
/**
@@ -235,7 +239,9 @@ public static MockMultipartHttpServletRequestBuilder multipart(HttpMethod httpMe
235239
* @since 5.0
236240
*/
237241
public static MockMultipartHttpServletRequestBuilder multipart(URI uri) {
238-
return new MockMultipartHttpServletRequestBuilder().uri(uri);
242+
MockMultipartHttpServletRequestBuilder builder = new MockMultipartHttpServletRequestBuilder();
243+
builder.uri(uri);
244+
return builder;
239245
}
240246

241247
/**
@@ -246,7 +252,9 @@ public static MockMultipartHttpServletRequestBuilder multipart(URI uri) {
246252
* @since 5.3.21
247253
*/
248254
public static MockMultipartHttpServletRequestBuilder multipart(HttpMethod httpMethod, URI uri) {
249-
return new MockMultipartHttpServletRequestBuilder(httpMethod).uri(uri);
255+
MockMultipartHttpServletRequestBuilder builder = new MockMultipartHttpServletRequestBuilder(httpMethod);
256+
builder.uri(uri);
257+
return builder;
250258
}
251259

252260
/**

0 commit comments

Comments
 (0)