Unit Testing File Upload REST API using Spring MVC MockMvc


Today a colleague asked me how he could unit test a Spring MVC REST resource. I am using Spring MVC test support for some time now so the obvious answer was to use MockMvc. In case you have not used MockMvc, it allows you to declaratively write tests for your Spring MVC controllers. Rather than calling controllers directly, you use the MockMvc fluent API to make a request to a URL and verify the response returned by the API. You can read Spring MVC documentation to learn about MockMvc in detail.

In this post, I will showcase how easily you can test Multipart request with Spring MVC test support.

Let’s suppose that we have a REST API to upload files as shown below in the code snippet.

import java.io.IOException;
import java.nio.file.Paths;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequestMapping("/api/files")
public class FileResource {

private String uploadPath;

public FileResource(@Value("upload.path") String uploadPath) {
this.uploadPath = uploadPath;
}

@PostMapping(path = "")
public ResponseEntity upload(@RequestParam("file") MultipartFile file) {
String filename = file.getOriginalFilename();
try {
file.transferTo(Paths.get(uploadPath, filename).toFile());
} catch (IOException e) {
throw new UnableToTransferFile(String.format("Unable to upload file %s to %s", filename, uploadPath));
}
return new ResponseEntity<>(HttpStatus.CREATED);
}
}

In the code shown above:

  1. We created a class FileResource and annotated it with @RestController. Also, we defined the @RequestMapping at the class level.
  2. Next, we created a method with name upload . The upload method accepts MultipartFile as request param.
  3. Next, we moved the file to a location specified as a @Value in the resource constructor.
  4. Finally, we returned HTTP status 201 if there was no exception in moving file to the uploadPath.

Now, let’s write unit test for the FileResource. As mentioned above, we will use MockMvc API in our test case. The test case for file upload is shown below.

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

public class FileResourceTests {

private MockMvc mockMvc;

@Rule
public TemporaryFolder folder = new TemporaryFolder();

@Before
public void setUp() throws Exception {
String uploadPath = folder.getRoot().getAbsolutePath();
mockMvc = MockMvcBuilders
.standaloneSetup(new FileResource(uploadPath))
.build();
}

@Test
public void should_upload_image_to_upload_path() throws Exception {
MockMultipartFile file = new MockMultipartFile("file", "hello.txt", MediaType.TEXT_PLAIN_VALUE, "Hello, World!".getBytes());
mockMvc.perform(fileUpload("/api/files").file(file))
.andDo(print())
.andExpect(status().isCreated());
assertThat(folder.getRoot().toPath().resolve("hello.txt")).exists();
}
}

There is a lot happening in the code shown above:

  1. In the setUp method, we created an instance of MockMvc using the standaloneSetup helper method. For unit tests, standaloneSetup is the recommended way. You registered FileResource Spring MVC controller so that you can test it.
  2. Next, we used JUnit Rule API to create temporary directory that will be used as the upload path. This will make sure directory is created before the test execution and cleaned up after the test execution.
  3. Next, in our test method we made a call to the /api/files endpoint using the fileupload request builder method. The fileupload is a wrapper around the HTTP POST method making it easy to test file upload scenario. We specified the file we want to upload.
  4. Finally, in our test we verified that we got HTTP status 201 in the response. Also, we asserted that file exists at upload path.

Leave a comment