使用 MockMultipartFile 测试最大上传文件大小

Test maximum upload file size with MockMultipartFile

我使用 Spring Boot 创建了一个文件上传服务,并使用 Spring Mock Mvc 和 MockMultipartFile 对其进行了测试。我想测试超过最大文件大小时是否抛出错误。以下测试失败,因为它收到 200。

RandomAccessFile f = new RandomAccessFile("t", "rw");
f.setLength(1024 * 1024 * 10);
InputStream is = Channels.newInputStream(f.getChannel());

MockMultipartFile firstFile = new MockMultipartFile("data", "file1.txt", "text/plain", is);

mvc.perform(fileUpload("/files")
    .file(firstFile))
    .andExpect(status().isInternalServerError());

是否可以测试上传文件的大小?

根据 documentation:

If the present length of the file as returned by the length method is smaller than the newLength argument then the file will be extended. In this case, the contents of the extended portion of the file are not defined.

试试这个:

byte[] bytes = new byte[1024 * 1024 * 10];
MockMultipartFile firstFile = new MockMultipartFile("data", "file1.txt", "text/plain", bytes);

这里有同样的问题。

不知道是否有更好的解决方案,但我创建了一个注释来验证上传的图像列表,并与其他人一起进行了检查。

  • Class 验证图像
    public class ImageValidator implements ConstraintValidator<ValidImage, List<MultipartFile>> {

      @Override
      public boolean isValid(
          List<MultipartFile> listMultipartFile, ConstraintValidatorContext context) {

        for (var multipartFile : listMultipartFile) {
          var msgValidation = imageValidations(multipartFile);

          if (!msgValidation.isEmpty()) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(msgValidation).addConstraintViolation();
            return false;
          }
        }

        return true;
      }

      private String imageValidations(MultipartFile multipartFile) {
        var contentType = multipartFile.getContentType();

        if (!isSupportedContentType(contentType)) {
          return String.format(
              "Only JPG and PNG images are allowed, %s provided.", multipartFile.getContentType());
        } else if (multipartFile.isEmpty()) {
          return "It must not be an empty image.";
        } else if (multipartFile.getSize() > (1024 * 1024)) {
          return "File size should be at most 1MB";
        }

        return "";
      }

      private boolean isSupportedContentType(String contentType) {
        var supportedContents = List.of("image/jpg", "image/jpeg", "image/png");
        return supportedContents.contains(contentType);
      }
    }
  • 界面
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = {ImageValidator.class})
    public @interface ValidImage {
      String message() default "Invalid image file";

      Class<?>[] groups() default {};

      Class<? extends Payload>[] payload() default {};
    }

不能用Spring的MockMultipartFile / MockMvc测试这个。原因是错误的根源不在 Spring 本身,而是在底层 Web 服务器(通常是 Tomcat),正如您在 MaxUploadSizeExceededException 的堆栈跟踪中看到的那样:

org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size of 500000 bytes exceeded; nested exception is org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (1065736) exceeds the configured maximum (500000)
    at org.springframework.web.multipart.commons.CommonsMultipartResolver.parseRequest(CommonsMultipartResolver.java:160)
    at org.springframework.web.multipart.commons.CommonsMultipartResolver.resolveMultipart(CommonsMultipartResolver.java:139)
[...]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Unknown Source)
Caused by: org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (1065736) exceeds the configured maximum (500000)
    at org.apache.commons.fileupload.FileUploadBase$FileItemIteratorImpl.<init>(FileUploadBase.java:965)
    at org.apache.commons.fileupload.FileUploadBase.getItemIterator(FileUploadBase.java:310)
    at org.apache.commons.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:334)
    at org.apache.commons.fileupload.servlet.ServletFileUpload.parseRequest(ServletFileUpload.java:115)
    at org.springframework.web.multipart.commons.CommonsMultipartResolver.parseRequest(CommonsMultipartResolver.java:156)
    ... 20 more

当使用默认 MockMvc 和 @SpringBootTest 及其默认设置时,没有启动真正的 Web 服务器,因此不会发生错误。

然而,您可以通过提供 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 告诉 Spring 为您的测试启动一个真正的 Web 服务器,这将(意外地)在一个随机端口上启动一个 Web 服务器。您可以使用 @LocalServerPort.

在测试 class 中访问该端口

然后您可以编写一个测试,对您的测试服务器执行真实分段上传,而不是伪造的。 REST Assured 是一个图书馆,除其他外,可以做到这一点:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MyTest {

    @LocalServerPort
    private int port;

    @Test
    void testMultipartUpload() throws Exception {
        File file = new File("my-file");
        RestAssured.baseURI = "http://localhost/api";
        RestAssured.port = port;
        Response res = given()
                .multiPart("data", file, "text/plain")
                .when().post("/upload");
        ...
    }

}

此测试会在您的上传过大时显示服务器错误。