'MockMVC - calling a PUT endpoint that accepts a multipart file

I have a simple PUT endpoint in a Spring Boot application:

@PutMapping()
public ResponseEntity<String> upload(@RequestParam("cats") MultipartFile file) throws IOException {

I'm trying to create a test for the controller using MockMVC:

import org.springframework.test.web.servlet.MockMvc;

@RunWith(SpringRunner.class)
@WebMvcTest(CatController.class)
public class CatControllerTest {

@Autowired
private MockMvc mockMvc;

...

For POST endpoints I use something like that:

 MockMultipartFile multipartFile = new MockMultipartFile("file", new FileInputStream("myCats.csv"));

 mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).alwaysDo(print()).build();

 MvcResult result = mockMvc.perform(MockMvcRequestBuilders.multipart("/uploadCats").file(multipartFile))
    .andExpect(status().isOk())
    .andReturn();

But when trying to translate the above call to PUT endpoint I find that I can create

MockMvcRequestBuilders.put("/catUpload")

But then I can't chain to it the multipart

Or I can do:

MockMvcRequestBuilders.multipart(...)

But then I can't chain to it the put.

I saw some post about this problem, but they all were few years old. Is there a way to do it?



Solution 1:[1]

Using this great example I was able to solve the issues.

I'm attaching here the code with the minor updates to the post from 2016:

        MockMultipartHttpServletRequestBuilder builder =
            MockMvcRequestBuilders.multipart("/catUpload");
            builder.with(request -> {
                request.setMethod("PUT");
                return request;
            });

     MvcResult result = mockMvc.perform(builder
            .file(multipartFile))
            .andExpect(status().isOk())
            .andReturn();

Solution 2:[2]

As of 2022 with Spring 5.3.17 it's possible to avoid explicit builder creation leveraging static multipart request mock builder that replaces deprecated fileUpload

Unfortunately it sill has method property hardcoded with POST value, forcing you to change it to PUT by its with method accepting a RequestPostProcessor as parameter.
Being the latter a Functional Interface, lets you reduce the code by lambda expression to something like this in one of its simplest form, involving just a path parameter with value of 99 and a single file to upload and expecting a 200 response code:

mockMvc.perform(multipart("/endpoint/{pathParameter}/upload_handler", 99)
  .file(mockFileToUpload)
  .with(req -> { req.setMethod("PUT"); return req; }))
.andExpect(status().isOk())

At current time I didn't find any RequestPostProcessor implementation giving the ability to apply needed method modification through a builder pattern, so we're still forced using void mutator setMethod and explicitly returning mutated argument.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 riorio
Solution 2 4javier