40

I have a controller's method with a PUT method, which receives multipart/form-data:

   @RequestMapping(value = "/putIn", method = RequestMethod.PUT)
   public Foo updateFoo(HttpServletRequest request,
                           @RequestBody Foo foo,
                           @RequestParam("foo_icon") MultipartFile file) {
    ...
   }

and I want to test it using MockMvc. Unfortunately MockMvcRequestBuilders.fileUpload creates essentially an instance of MockMultipartHttpServletRequestBuilder which has a POST method:

super(HttpMethod.POST, urlTemplate, urlVariables)

EDIT: Surely I can I can not create my own implementation of MockHttpServletRequestBuilder, say

public MockPutMultipartHttpServletRequestBuilder(String urlTemplate, Object... urlVariables) {
    super(HttpMethod.PUT, urlTemplate, urlVariables);
    super.contentType(MediaType.MULTIPART_FORM_DATA);
}

because MockHttpServletRequestBuilder has a package-local constructor.

But I'm wondering is there any more convenient Is any way to do this, may be I missed some existent class or method for doing this?

Andremoniy
  • 34,031
  • 20
  • 135
  • 241
  • Could you please mark my answer as the accepted answer? It will help keep StackOverflow clean and efficient. Thanks! – HammerNL Sep 20 '17 at 14:14

4 Answers4

92

Yes, there is a way, and it's simple too!

I ran into the same problem myself. Though I was discouraged by Sam Brannen's answer, it appears that Spring MVC nowadays DOES support PUT file uploading as I could simply do such a request using Postman (I'm using Spring Boot 1.4.2). So, I kept digging and found that the only problem is the fact that the MockMultipartHttpServletRequestBuilder returned by MockMvcRequestBuilders.fileUpload() has the method hardcoded to "POST". Then I discovered the with() method...

and that allowed me to come up with this neat little trick to force the MockMultipartHttpServletRequestBuilder to use the "PUT" method anyway:

    MockMultipartFile file = new MockMultipartFile("data", "dummy.csv",
            "text/plain", "Some dataset...".getBytes());

    MockMultipartHttpServletRequestBuilder builder =
            MockMvcRequestBuilders.multipart("/test1/datasets/set1");
    builder.with(new RequestPostProcessor() {
        @Override
        public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
            request.setMethod("PUT");
            return request;
        }
    });
    mvc.perform(builder
            .file(file))
            .andExpect(status().isOk());

  Works like a charm!

Nalyd
  • 95
  • 1
  • 16
HammerNL
  • 1,689
  • 18
  • 22
5

This is unfortunately currently not supported in Spring MVC Test, and I don't see a work-around other than creating your own custom MockPutMultipartHttpServletRequestBuilder and copying-n-pasting code from the standard implementation.

For what it's worth, Spring MVC also does not support PUT requests for file uploads by default either. The Multipart resolvers are hard coded to accept only POST requests for file uploads -- both for Apache Commons and the standard Servlet API support.

If you would like Spring to support PUT requests in addition, feel free to open a ticket in Spring's JIRA issue tracker.

Sam Brannen
  • 29,611
  • 5
  • 104
  • 136
  • I found a simple solution! Check out my answer – HammerNL Nov 17 '16 at 13:42
  • Spring now also supports any method, not just POST. See [this commit](https://github.com/spring-projects/spring-framework/commit/bac68c8d3f9ca4caa70b54c14e0e9805f88f8d34#diff-baffaa8d24133e1e64e3e019e2dd877d1fb0f0f2dc43a79fb7567f39dd8f2536) – Wim Deblauwe Nov 05 '21 at 11:24
5

Translating @HammerNl answer for Kotlin. This worked for me.

val file = File("/path/to/file").readBytes()
val multipartFile = MockMultipartFile("image", "image.jpg", "image/jpg", file)

val postProcess = RequestPostProcessor { it.method = "PUT"; it}
mockMvc.perform(
    MockMvcRequestBuilders.multipart("/api/image/$id")
        .file(multipartFile)
        .with(postProcess))
        .andExpect(MockMvcResultMatchers.status().isOk)
orpheus
  • 1,060
  • 2
  • 15
  • 23
0

You can pass both foo and file

Try rewrite you controller like:

@RequestMapping(value = "/putIn", method = RequestMethod.PUT)
public Foo updateFoo(
    HttpServletRequest request,
    @RequestPart Foo foo,
    @RequestPart MultipartFile file) {
    ...
}

And test looks like:

    MockMultipartFile file = new MockMultipartFile("file", "dummy.csv",
            "text/plain", "Some dataset...".getBytes());
    // application/json if you pass json as string
    MockMultipartFile file2 = new MockMultipartFile("foo", "foo.txt",
            "application/json", "Foo data".getBytes());

    MockMultipartHttpServletRequestBuilder builder =
            MockMvcRequestBuilders.multipart("/test1/datasets/set1");
    builder.with(new RequestPostProcessor() {
        @Override
        public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
            request.setMethod("PUT");
            return request;
        }
    });
    mvc.perform(builder
            .file(file)
            .file(file2))
            .andExpect(status().ok());
lalilulelo_1986
  • 526
  • 3
  • 7
  • 18
  • Hello. Just tried to do this but I keep getting "Content type 'application/json' not supported" for mockMvc test. I have a controller with two RequestPart, one of them is a POJO and the other a MultipartFile. What I'm doing wrong? Using postman is working fine! – Lucas Soares Apr 01 '21 at 14:44