Using Spring MVC Test to unit test multipart POST request

SpringUnit TestingSpring Mvc

Spring Problem Overview


I have the following request handler for saving autos. I have verified that this works when I use e.g. cURL. Now I want to unit test the method with Spring MVC Test. I have tried to use the fileUploader, but I am not managing to get it working. Nor do I manage to add the JSON part.

How would I unit test this method with Spring MVC Test? I am not able to find any examples on this.

@RequestMapping(value = "autos", method = RequestMethod.POST)
public ResponseEntity saveAuto(
    @RequestPart(value = "data") autoResource,
    @RequestParam(value = "files[]", required = false) List<MultipartFile> files) {
    // ...
}

I want to uplod a JSON representation for my auto + one or more files.

I will add 100 in bounty to the correct answer!

Spring Solutions


Solution 1 - Spring

Since MockMvcRequestBuilders#fileUpload is deprecated, you'll want to use MockMvcRequestBuilders#multipart(String, Object...) which returns a MockMultipartHttpServletRequestBuilder. Then chain a bunch of file(MockMultipartFile) calls.

Here's a working example. Given a @Controller

@Controller
public class NewController {

	@RequestMapping(value = "/upload", method = RequestMethod.POST)
	@ResponseBody
	public String saveAuto(
			@RequestPart(value = "json") JsonPojo pojo,
			@RequestParam(value = "some-random") String random,
			@RequestParam(value = "data", required = false) List<MultipartFile> files) {
		System.out.println(random);
		System.out.println(pojo.getJson());
		for (MultipartFile file : files) {
			System.out.println(file.getOriginalFilename());
		}
		return "success";
	}

	static class JsonPojo {
		private String json;

		public String getJson() {
			return json;
		}

		public void setJson(String json) {
			this.json = json;
		}

	}
}

and a unit test

@WebAppConfiguration
@ContextConfiguration(classes = WebConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Example {

	@Autowired
	private WebApplicationContext webApplicationContext;

	@Test
	public void test() throws Exception {

		MockMultipartFile firstFile = new MockMultipartFile("data",	"filename.txt", "text/plain", "some xml".getBytes());
		MockMultipartFile secondFile = new MockMultipartFile("data", "other-file-name.data", "text/plain", "some other type".getBytes());
		MockMultipartFile jsonFile = new MockMultipartFile("json", "", "application/json", "{\"json\": \"someValue\"}".getBytes());

		MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
		mockMvc.perform(MockMvcRequestBuilders.multipart("/upload")
						.file(firstFile)
						.file(secondFile)
                        .file(jsonFile)
						.param("some-random", "4"))
					.andExpect(status().is(200))
					.andExpect(content().string("success"));
	}
}

And the @Configuration class

@Configuration
@ComponentScan({ "test.controllers" })
@EnableWebMvc
public class WebConfig extends WebMvcConfigurationSupport {
	@Bean
	public MultipartResolver multipartResolver() {
		CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
		return multipartResolver;
	}
}

The test should pass and give you output of

4 // from param
someValue // from json file
filename.txt // from first file
other-file-name.data // from second file

The thing to note is that you are sending the JSON just like any other multipart file, except with a different content type.

Solution 2 - Spring

The method MockMvcRequestBuilders.fileUpload is deprecated use MockMvcRequestBuilders.multipart instead.

This is an example:

import static org.hamcrest.CoreMatchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.multipart.MultipartFile;


/**
 * Unit test New Controller.
 *
 */
@RunWith(SpringRunner.class)
@WebMvcTest(NewController.class)
public class NewControllerTest {

	private MockMvc mockMvc;
	
	@Autowired
    WebApplicationContext wContext;

	@MockBean
	private NewController newController;
	
	@Before
	public void setup() {
		this.mockMvc = MockMvcBuilders.webAppContextSetup(wContext)
	               .alwaysDo(MockMvcResultHandlers.print())
	               .build();
	}
	
   @Test
	public void test() throws Exception {
	   // Mock Request
		MockMultipartFile jsonFile = new MockMultipartFile("test.json", "", "application/json", "{\"key1\": \"value1\"}".getBytes());

		// Mock Response
		NewControllerResponseDto response = new NewControllerDto();
		Mockito.when(newController.postV1(Mockito.any(Integer.class), Mockito.any(MultipartFile.class))).thenReturn(response);
		
		mockMvc.perform(MockMvcRequestBuilders.multipart("/fileUpload")
				.file("file", jsonFile.getBytes())
				.characterEncoding("UTF-8"))
		.andExpect(status().isOk());
		
	}

}

Solution 3 - Spring

Have a look at this example taken from the spring MVC showcase, this is the link to the source code:

@RunWith(SpringJUnit4ClassRunner.class)
public class FileUploadControllerTests extends AbstractContextControllerTests {

    @Test
    public void readString() throws Exception {

        MockMultipartFile file = new MockMultipartFile("file", "orig", null, "bar".getBytes());

        webAppContextSetup(this.wac).build()
            .perform(fileUpload("/fileupload").file(file))
            .andExpect(model().attribute("message", "File 'orig' uploaded successfully"));
    }

}

Solution 4 - Spring

Here's what worked for me, here I'm attaching a file to my EmailController under test. Also take a look at the postman screenshot on how I'm posting the data.

    @WebAppConfiguration
    @RunWith(SpringRunner.class)
    @SpringBootTest(
    		classes = EmailControllerBootApplication.class
    	)
    public class SendEmailTest {
    
    	@Autowired
        private WebApplicationContext webApplicationContext;
    	
    	@Test
    	public void testSend() throws Exception{
    		String jsonStr = "{\"to\": [\"[email protected]\"],\"subject\": "    				+ "\"CDM - Spring Boot email service with attachment\","    				+ "\"body\": \"Email body will contain  test results, with screenshot\"}";    		    	    Resource fileResource = new ClassPathResource(    	    		"screen-shots/HomePage-attachment.png");      	    assertNotNull(fileResource);    	    MockMultipartFile firstFile = new MockMultipartFile(                        "attachments",fileResource.getFilename(),                        MediaType.MULTIPART_FORM_DATA_VALUE,                        fileResource.getInputStream());      	                assertNotNull(firstFile);    	        	            MockMvc mockMvc = MockMvcBuilders.                  webAppContextSetup(webApplicationContext).build();        	                  mockMvc.perform(MockMvcRequestBuilders                   .multipart("/api/v1/email/send")        	    	.file(firstFile)        	    	.param("data", jsonStr))        	    	.andExpect(status().is(200));        	}        }

Postman Request

Solution 5 - Spring

If you are using Spring4/SpringBoot 1.x, then it's worth mentioning that you can add "text" (json) parts as well . This can be done via MockMvcRequestBuilders.fileUpload().file(MockMultipartFile file) (which is needed as method .multipart() is not available in this version):

@Test
public void test() throws Exception {

   mockMvc.perform( 
       MockMvcRequestBuilders.fileUpload("/files")
         // file-part
         .file(makeMultipartFile( "file-part" "some/path/to/file.bin", "application/octet-stream"))
        // text part
         .file(makeMultipartTextPart("json-part", "{ \"foo\" : \"bar\" }", "application/json"))
       .andExpect(status().isOk())));

   }

   private MockMultipartFile(String requestPartName, String filename, 
       String contentType, String pathOnClassPath) {

       return new MockMultipartFile(requestPartName, filename, 
          contentType, readResourceFile(pathOnClasspath);
   }

   // make text-part using MockMultipartFile
   private MockMultipartFile makeMultipartTextPart(String requestPartName, 
       String value, String contentType) throws Exception {

       return new MockMultipartFile(requestPartName, "", contentType,
               value.getBytes(Charset.forName("UTF-8")));   
   }


   private byte[] readResourceFile(String pathOnClassPath) throws Exception {
      return Files.readAllBytes(Paths.get(Thread.currentThread().getContextClassLoader()
         .getResource(pathOnClassPath).toUri()));
   }

}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLuckyLukeView Question on Stackoverflow
Solution 1 - SpringSotirios DelimanolisView Answer on Stackoverflow
Solution 2 - SpringRomina LiuzziView Answer on Stackoverflow
Solution 3 - SpringAngular UniversityView Answer on Stackoverflow
Solution 4 - SpringAlferd NobelView Answer on Stackoverflow
Solution 5 - SpringwalkerosView Answer on Stackoverflow