使用具有独立上下文和 SpringBoot 1.2.5 的 MockMvcBuilders 进行文件上传的单元测试

Unit Test of file upload using MockMvcBuilders with standalone context and SpringBoot 1.2.5

我正在使用 Spring Boot 1.2.5-RELEASE。我有一个控制器接收 MultipartFileString

@RestController
@RequestMapping("file-upload")
public class MyRESTController {

  @Autowired
  private AService aService;

  @RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
  @ResponseStatus(HttpStatus.CREATED)
  public void fileUpload(
      @RequestParam(value = "file", required = true) final MultipartFile file,
      @RequestParam(value = "something", required = true) final String something) {
   aService.doSomethingOnDBWith(file, value);
  }
}

现在,该服务运行良好。我用 PostMan 测试了它,一切都按预期进行。 不幸的是,我无法为该代码编写独立的单元测试。当前单元测试是:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApplication.class)
@WebAppConfiguration
public class ControllerTest{

    MockMvc mockMvc;

    @Mock
    AService aService;

    @InjectMocks
    MyRESTController controller;

  @Before public void setUp(){
    MockitoAnnotations.initMocks(this);
    this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
  }

  @Test
  public void testFileUpload() throws Exception{
        final File file = getFileFromResource(fileName);
        //File is correctly loaded
        final MockMultipartFile multipartFile = new MockMultipartFile("aMultiPartFile.txt", new FileInputStream(file));

        doNothing().when(aService).doSomethingOnDBWith(any(MultipartFile.class), any(String.class));

        mockMvc.perform(
                post("/file-upload")
                        .requestAttr("file", multipartFile.getBytes())
                        .requestAttr("something", ":(")
                        .contentType(MediaType.MULTIPART_FORM_DATA_VALUE))
                .andExpect(status().isCreated());
    }
}

测试失败

java.lang.IllegalArgumentException: Expected MultipartHttpServletRequest: is a MultipartResolver configured?

现在,在 Spring 引导的 MultipartAutoConfiguration class 中,我看到 MultipartResolver 是自动配置的。但是,我想使用 MockMvcBuildersstandaloneSetup 我无法访问它。

我尝试了几种单元测试配置,为简洁起见我没有报告。特别是,我还尝试了 here 所示的放心,但老实说这不起作用,因为我似乎无法模拟 AService 实例。

有什么解决办法吗?

您正在尝试将此处的单元测试 (standaloneSetup(controller).build();) 与 Spring 集成测试 (@RunWith(SpringJUnit4ClassRunner.class)) 结合起来。

做一个或另一个。

  1. 集成测试需要使用类似下面的代码。问题是伪造豆子。有一些方法可以使用 @Primary 注释和 @Profile 注释来伪造这样的 bean(您创建将覆盖主要生产 bean 的测试 bean)。我有一些这样伪造 Spring beans 的例子(例如 this bean is replaced by this bean in this test)。

    @Autowired
    private WebApplicationContext webApplicationContext;
    
    @BeforeMethod
    public void init() {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
    
  2. 第二个选项是删除测试和测试控制器上的 @RunWith(SpringJUnit4ClassRunner.class) 和其他 class 级别配置,没有 Spring 独立设置的上下文。这样您就无法在控制器上测试验证注释,但可以使用 Spring MVC 注释。优点是可以通过 Mockito 伪造 bean(例如通过 InjectMocks 和 Mock 注释)

错误表明请求不是多部分请求。换句话说,到那时它应该已经被解析了。但是在 MockMvc 测试中没有实际请求。这只是模拟请求和响应。因此,您需要使用 perform.fileUpload(...) 来设置模拟文件上传请求。

我混合了 lkrnak 建议的内容和 Mockito @Spy 的功能。我使用 REST-Assured 来调用。所以,我做了如下:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApplication.class)
@WebAppConfiguration
@IntegrationTest({"server.port:0"})
public class ControllerTest{

    {
      System.setProperty("spring.profiles.active", "unit-test");
    }


    @Autowired
    @Spy
    AService aService;

    @Autowired
    @InjectMocks
    MyRESTController controller;

    @Value("${local.server.port}")
    int port;    


  @Before public void setUp(){
    RestAssured.port = port;

    MockitoAnnotations.initMocks(this);
  }

  @Test
  public void testFileUpload() throws Exception{
        final File file = getFileFromResource(fileName);

        doNothing().when(aService)  
             .doSomethingOnDBWith(any(MultipartFile.class), any(String.class));

        given()
          .multiPart("file", file)
          .multiPart("something", ":(")
          .when().post("/file-upload")
          .then().(HttpStatus.CREATED.value());
    }
}

服务定义为

@Profile("unit-test")
@Primary
@Service
public class MockAService implements AService {
  //empty methods implementation
}