Affects version: 5.3.3 Related to: #26261

I have a controller method that accepts multipart/form-data requests with 2 parameters: a file and a @RequestPart String label. Previously in my tests I was creating a MockPart for the label and it was working fine: new MockPart("label", "mainImage".getBytes(UTF_8)).

With the latest changes in 5.3.3 the MockPart is converted to a request parameter instead of a multipart boundary and I don't receive anything for the RequestPart in the controller thus breaking the tests.

Why is it necessary to convert MockParts to parameters in a multipart request? I'm not sure if this is strictly related to the value being a parameter of if there is another underlying issue. I thought that I should receive the value by using either RequestPart or RequestParam just using different converters.

Comment From: rstoyanchev

This is likely a duplicate of #26400. Could you check if it works 5.3.4-SNAPSHOT?

Comment From: LickIt

Yes, I already tried running it with spring-test:5.3.4-SNAPSHOT but the result is still a "null" value in the controller.

Comment From: LickIt

I extracted a simple snippet for testing: https://github.com/LickIt/spring-mockpart-issue The test is passing with spring-test 5.3.2 but not with 5.3.3 and 5.3.4-SNAPSHOT

@Test
public void testUpload() throws Exception {
    MockMultipartFile file = new MockMultipartFile("file", "file.png", "application/octet-stream", new byte[0]);
    MockPart label = new MockPart("label", "mainImage".getBytes(UTF_8));

    mockMvc.perform(multipart("/test").file(file).part(label))
            .andExpect(content().string("mainImage"));
}

Here is the controller method:

@PostMapping(path = "/test", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String uploadFile(@RequestParam("file") MultipartFile file,
                         @RequestPart(value = "label", required = false) String label) {
    return label;
}

Comment From: rstoyanchev

Thanks for the sample.

The latest code adds both a Part and a request parameter, this is not the issue. The root cause appears to be in the way StandardMultipartHttpServletRequest and MockMultipartHttpServletRequest implement getMultipartHeaders(String). The former always returns headers (possibly empty) for a part that is present. The latter returns null if the part doesn't have a Content-Type. Then RequestPartServletServerHttpRequest takes this to mean there is no such part at all. In effect, currently, a MockPart has to have a Content-Type to work with @RequestPart. The issue is temporarily masked in 5.3.2 due to the use of StandardMultipartHttpServletRequest but in previous versions, including 5.2.x the same issue is present.

From MultipartHttpServletRequest#getMultipartHeaders:

 * Return the headers associated with the specified part of the multipart request.
 * <p>If the underlying implementation supports access to headers, then all headers are returned.
 * Otherwise, the returned headers will include a 'Content-Type' header at the very least.

From RFC 7578 says:

   Each part MAY have an (optional) "Content-Type" header field, which
   defaults to "text/plain".  If the contents of a file are to be sent,
   the file data SHOULD be labeled with an appropriate media type, if
   known, or "application/octet-stream".

I think the Javadoc for MultipartHttpServletRequest#getMultipartHeaders is too strict since a Content-Type may legitimately not be present. We can align MockMultipartHttpServletRequest to work like StandardMultipartHttpServletRequest by returning the available headers, possibly empty. That has a better chance of decoding correctly based on the target Class.

What do you think @jhoeller ?

Comment From: rstoyanchev

This should be fixed in the latest snapshots. If you could, please give it another try.

Comment From: LickIt

@rstoyanchev Thank you for the quick response and resolution! I can confirm this is fixed in the latest snapshot.

Comment From: rstoyanchev

Thanks for checking.