Matt Matt - 1 month ago 17
reST (reStructuredText) Question

How to Post multipart/form-data for a File Upload using SpringMVC and MockMVC

I've created a photo uploader that works great using javax.ws.rs. Here's the signature and basic gist of it:

@POST
@Path("/upload/photo")
@Consumes("multipart/form-data")
@Produces("application/json")
public String uploadPhoto(InputStream stream){
try {
int read = 0;
FileOutputStream fos = new FileOutputStream(file);
CountingOutputStream out = new CountingOutputStream(fos);
byte[] bytes = new byte[MAX_UPLOAD_SIZE];

while ((read = stream.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
out.flush();
out.close();
} catch (IOException e) {
// TODO throw!
e.printStackTrace();
}
//...
}


I can test this using apache.commons.httpClient library like this:

@Test
public void testUpload() {

int statusCode = 0;
String methodResult = null;

String endpoint = SERVICE_HOST + "/upload/photo";

PostMethod post = new PostMethod(endpoint);

File file = new File("/home/me/Desktop/someFolder/image.jpg");

FileRequestEntity entity = new FileRequestEntity(file, "multipart/form-data");

post.setRequestEntity(entity);

try {
httpClient.executeMethod(post);
methodResult = post.getResponseBodyAsString();
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

statusCode = post.getStatusCode();

post.releaseConnection();
//...
}


This works great! The problem is that the rest of the application is written using Spring MVC. When I use Spring Mock MVC testing framework the program just hangs (shown in the code snippet below this one). Here is the SpringMVC code for the uploader:

@ResponseBody
@RequestMapping( produces="application/json",
consumes="multipart/form-data",
method=RequestMethod.POST,
value="/photo")
public String uploadPhoto(@RequestPart("file") MultipartFile multipartFile){

try {
int read = 0;
FileOutputStream fos = new FileOutputStream(file);
CountingOutputStream out = new CountingOutputStream(fos);
byte[] bytes = new byte[MAX_UPLOAD_SIZE];

while ((read = multipartFile.getInputStream().read(bytes)) != -1) {
out.write(bytes, 0, read);
}

out.flush();
out.close();

} catch (IOException e) {
// TODO throw!
e.printStackTrace();
}
//...
}


And below is what I've implemented for testing, using Spring Mock MVC. I think the problem has to do with using fileUpload(...). Is there a way to test using the normal post(..) instead, like I can with apache? I'd prefer to use an InputStream as the argument and avoid using a MultipartFile.

@Test
public void testUpload() throws Exception {

String endpoint = BASE_URL + "/upload/photo";

FileInputStream fis = new FileInputStream("/home/me/Desktop/someFolder/image.jpg");
MockMultipartFile multipartFile = new MockMultipartFile("file", fis);

mockMvc.perform(fileUpload(endpoint)
.file(multipartFile)
.contentType(MediaType.MULTIPART_FORM_DATA))
.andExpect(status().isOk());

}


Ideally, I'd like to use Spring MVC and the Spring Mock MVC framework, but the code I've provided just hangs on the while statement. Is what I'm doing correct as far as using the fileUpload method in the Spring test? Any advice is appreciated.

Answer
  1. To add content to a mock post request use content(bytes[])
  2. media type parameter boundary was necessary

Also, it was safe to use a plain old InputStream from java.io as a parameter for the upload method, and still use MockMultipartFile in the request.

@Test
public void testUpload() throws Exception {

            String endpoint = BASE_URL + "/upload/photo";

            FileInputStream fis = new FileInputStream("/home/me/Desktop/someDir/image.jpg");
            MockMultipartFile multipartFile = new MockMultipartFile("file", fis);

            HashMap<String, String> contentTypeParams = new HashMap<String, String>();
            contentTypeParams.put("boundary", "265001916915724");
            MediaType mediaType = new MediaType("multipart", "form-data", contentTypeParams);

            mockMvc.perform(
                    post(endpoint)
                    .content(multipartFile.getBytes())
                    .contentType(mediaType))
                    .andExpect(status().isOk());
}