diff --git a/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java b/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java
index e975586ceb1..58dbbfb4b57 100644
--- a/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java
+++ b/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java
@@ -36,6 +36,7 @@ import org.jspecify.annotations.Nullable;
import org.w3c.dom.Node;
import org.springframework.core.io.Resource;
+import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequest;
@@ -205,6 +206,7 @@ public class ContentRequestMatchers {
*
{@link HttpEntity} - entity with a Resource or byte[], and a Content-Type header
*
* Note: This method uses the fork of Commons FileUpload library
* packaged with Apache Tomcat in the {@code org.apache.tomcat.util.http.fileupload}
@@ -239,7 +241,7 @@ public class ContentRequestMatchers {
return multipartData(map, DEFAULT_MULTIPART_ENCODING, false);
}
- @SuppressWarnings("ConstantConditions")
+ @SuppressWarnings({"ConstantConditions", "unchecked"})
private RequestMatcher multipartData(
MultiValueMap expectedMap, Charset defaultCharset, boolean containsExactly) {
@@ -256,27 +258,45 @@ public class ContentRequestMatchers {
values.size() == actualMap.get(name).size() :
values.size() <= actualMap.get(name).size());
for (int i = 0; i < values.size(); i++) {
+ MediaType expectedContentType = null;
Object expected = values.get(i);
Object actual = actualMap.get(name).get(i);
+
+ if (expected instanceof HttpEntity> entity) {
+ expectedContentType = entity.getHeaders().getContentType();
+ expected = entity.getBody();
+ }
if (expected instanceof Resource resource) {
expected = StreamUtils.copyToByteArray(resource.getInputStream());
}
+
if (expected instanceof byte[]) {
- assertTrue("Multipart is not a file", actual instanceof byte[]);
- assertEquals("Multipart content", expected, actual);
+ assertEquals("Multipart content", expected, asHttpEntity(actual).getBody());
}
else if (expected instanceof String) {
assertTrue("Multipart is not a String", actual instanceof String);
assertEquals("Multipart content", expected, actual);
}
else {
- throw new IllegalArgumentException("Unexpected multipart value: " + expected.getClass());
+ throw new IllegalArgumentException("Unexpected multipart value: " +
+ (expected != null ? expected.getClass() : null));
+ }
+
+ if (expectedContentType != null) {
+ assertEquals("Multipart content-type",
+ expectedContentType, asHttpEntity(actual).getHeaders().getContentType());
}
}
}
};
}
+ @SuppressWarnings("unchecked")
+ private static HttpEntity asHttpEntity(Object actual) {
+ assertTrue("Multipart is not an HttpEntity", actual instanceof HttpEntity>);
+ return (HttpEntity) actual;
+ }
+
/**
* Parse the request body and the given String as XML and assert that the
* two are "similar" - i.e. they contain the same elements and attributes
@@ -444,7 +464,7 @@ public class ContentRequestMatchers {
MultiValueMap result = new LinkedMultiValueMap<>();
for (FileItem fileItem : fileItems) {
result.add(fileItem.getFieldName(),
- (fileItem.isFormField() ? fileItem.getString() : fileItem.get()));
+ (fileItem.isFormField() ? fileItem.getString() : toHttpEntity(fileItem)));
}
return result;
}
@@ -452,6 +472,12 @@ public class ContentRequestMatchers {
throw new IllegalStateException("Failed to parse multipart request", ex);
}
}
+
+ private static HttpEntity> toHttpEntity(FileItem fileItem) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.set(HttpHeaders.CONTENT_TYPE, fileItem.getContentType());
+ return new HttpEntity<>(fileItem.get(), headers);
+ }
}
}
diff --git a/spring-test/src/test/java/org/springframework/test/web/client/match/MultipartRequestMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/client/match/MultipartRequestMatchersTests.java
index c105ef21eab..0375a5086d9 100644
--- a/spring-test/src/test/java/org/springframework/test/web/client/match/MultipartRequestMatchersTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/client/match/MultipartRequestMatchersTests.java
@@ -23,6 +23,7 @@ import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
@@ -160,6 +161,20 @@ public class MultipartRequestMatchersTests {
assertThatExceptionOfType(AssertionError.class).isThrownBy(this::writeAndAssert);
}
+ @Test // gh-36154
+ public void testHttpEntityMatch() throws Exception {
+ String contentType = "text/plain";
+ MultipartFile file = new MockMultipartFile("file", "foo.txt", contentType, "Foo Lorem ipsum".getBytes());
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.set(HttpHeaders.CONTENT_TYPE, contentType);
+ this.input.add("foo.txt", new HttpEntity<>(file.getResource(), headers));
+
+ this.expected.addAll(this.input);
+
+ writeAndAssert();
+ }
+
private void writeAndAssert() throws IOException {
writeForm();