Many developers get stuck after implementing a file upload API, unsure how to write a success-path test with MockMultipartFile, or how to verify that a 400 is returned for an oversized file or an invalid MIME type.

This article focuses specifically on testing upload APIs with MockMultipartFile and perform(multipart()). Testing the download API is covered in How to Test a File Download API with MockMvc, the upload implementation itself in How to Use MultipartFile, and the basics of @WebMvcTest and MockMvc in Unit Testing Controllers with MockMvc. This article concentrates on verifying the accept/reject logic.

A Minimal Sample API Under Test

We’ll assume a Controller that receives a @RequestParam MultipartFile and simply passes it to the Service’s store() method. Inside store(), the size and MIME type are validated, and an InvalidFileException is thrown on violation. In the test, we mock FileUploadService using @WebMvcTest and @MockitoBean (@MockBean has been consolidated into @MockitoBean and deprecated since Spring Boot 3.4, so use @MockitoBean for new code).

Basic Creation of MockMultipartFile

The constructor takes four arguments: name, originalFilename, contentType, and content (byte[]).

MockMultipartFile file = new MockMultipartFile(
    "file",        // must match the @RequestParam name
    "sample.png",   // original filename
    "image/png",     // Content-Type
    "dummy-image-content".getBytes(StandardCharsets.UTF_8)
);

Even if name doesn’t match the Controller’s @RequestParam name, Spring MVC’s standard error handling will still return a 400. However, note that it won’t use the custom error body format defined in your @ControllerAdvice — it will use Spring’s default error message instead — so your jsonPath() assertions may fail in a way that isn’t immediately obvious.

If you want to test an empty file (0 bytes), pass new byte[0] instead of null for content.

Building a Success-Path Request with perform(multipart())

Stack parts onto multipart() with .file() and send the request. Stub the Service’s return value beforehand.

when(fileUploadService.store(any()))
    .thenReturn(new FileUploadResponse("sample.png", 123L));

mockMvc.perform(multipart("/api/files").file(file))
    .andExpect(status().isCreated())
    .andExpect(jsonPath("$.filename").value("sample.png"));

Failure-Path Test for Exceeding the File Size Limit

Prepare a byte array that exceeds the limit, have the Service throw an exception, and verify the 400 response.

byte[] oversized = new byte[11 * 1024 * 1024]; // exceeds the 10MB limit

MockMultipartFile bigFile = new MockMultipartFile(
    "file", "big.png", "image/png", oversized
);

when(fileUploadService.store(any()))
    .thenThrow(new InvalidFileException("File size exceeds the limit (10MB)"));

mockMvc.perform(multipart("/api/files").file(bigFile))
    .andExpect(status().isBadRequest())
    .andExpect(jsonPath("$.message").value("File size exceeds the limit (10MB)"));

The MaxUploadSizeExceededException raised on the servlet side when spring.servlet.multipart.max-file-size is exceeded can be hard to reproduce, because the multipart handling used by MockMvc in @WebMvcTest doesn’t go through the servlet container’s actual multipart parser (see Spring Boot’s multipart configuration for details). For this reason, it’s more practical for this article to focus testing on the custom validation inside the Service.

Failure-Path Tests for Invalid MIME Types and Extensions

Grouping multiple invalid patterns with @ParameterizedTest makes the tests easier to read.

@ParameterizedTest
@ValueSource(strings = {"application/octet-stream", "text/plain", "application/zip"})
void rejectsDisallowedContentType(String contentType) throws Exception {
    MockMultipartFile invalidFile = new MockMultipartFile(
        "file", "sample.exe", contentType, "dummy".getBytes()
    );

    when(fileUploadService.store(any()))
        .thenThrow(new InvalidFileException("This file type is not allowed"));

    mockMvc.perform(multipart("/api/files").file(invalidFile))
        .andExpect(status().isBadRequest())
        .andExpect(jsonPath("$.message").value("This file type is not allowed"));
}

You can extend this with the same structure for cases where only the extension is changed while the contentType stays valid, or vice versa. Real clients sometimes send a contentType with a charset attached, like image/png; charset=UTF-8, or with mixed case. If your allow-list check uses a simple equals() comparison, legitimate files can be incorrectly rejected — it’s worth adding these cases to your @ParameterizedTest as well, just to be safe.

The Relationship Between Exception Handlers and Tests

What the test should verify is not the exception class, but the HTTP response returned to the client. The implementation details of @ControllerAdvice are covered in Exception Handling for REST APIs.

@ExceptionHandler(InvalidFileException.class)
public ResponseEntity<ErrorResponse> handleInvalidFile(InvalidFileException e) {
    return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage()));
}

By having the Service throw the exception via @MockitoBean, you can verify just the status and body without needing to be aware of the handler’s internals. If you get a different status than expected, use andDo(print()) to inspect the actual request/response content — it helps you find the cause quickly.

Testing Multiple Files and Form Fields Together

For multiple files, call .file() multiple times; for form fields, add them with .param().

mockMvc.perform(multipart("/api/files/batch")
        .file(file1)
        .file(file2)
        .param("category", "document"))
    .andExpect(status().isCreated());

By making only one file have an invalid MIME type, you can use the same structure to verify the case where “only one out of multiple files is rejected.”

Summary

Covering these three patterns — the success path, exceeding the size limit, and an invalid MIME type — gives you sufficient test coverage for an upload API in practice. Remember the flow: prepare test data with MockMultipartFile, build the request with perform(multipart()), and trigger exceptions via @MockitoBean to verify the response. This pattern applies broadly. The download side is covered in How to Test a File Download API with MockMvc.