Uso de Spring MVC Test para probar unitariamente la solicitud POST de varias partes

115

Tengo el siguiente controlador de solicitudes para guardar autos. He verificado que esto funciona cuando uso, por ejemplo, cURL. Ahora quiero hacer una prueba unitaria del método con Spring MVC Test. He intentado utilizar fileUploader, pero no estoy logrando que funcione. Tampoco consigo agregar la parte JSON.

¿Cómo probaría este método con Spring MVC Test? No puedo encontrar ningún ejemplo sobre esto.

@RequestMapping(value = "autos", method = RequestMethod.POST)
public ResponseEntity saveAuto(
    @RequestPart(value = "data") autoResource,
    @RequestParam(value = "files[]", required = false) List<MultipartFile> files) {
    // ...
}

Quiero subir una representación JSON para mi auto + uno o más archivos.

¡Agregaré 100 en recompensa a la respuesta correcta!

LuckyLuke
fuente

Respuestas:

256

Dado que MockMvcRequestBuilders#fileUploadestá obsoleto, querrá usarMockMvcRequestBuilders#multipart(String, Object...) que devuelve un MockMultipartHttpServletRequestBuilder. Luego encadena un montón de file(MockMultipartFile)llamadas.

Aquí tienes un ejemplo práctico. Dado un@Controller

@Controller
public class NewController {

    @RequestMapping(value = "/upload", method = RequestMethod.POST)
    @ResponseBody
    public String saveAuto(
            @RequestPart(value = "json") JsonPojo pojo,
            @RequestParam(value = "some-random") String random,
            @RequestParam(value = "data", required = false) List<MultipartFile> files) {
        System.out.println(random);
        System.out.println(pojo.getJson());
        for (MultipartFile file : files) {
            System.out.println(file.getOriginalFilename());
        }
        return "success";
    }

    static class JsonPojo {
        private String json;

        public String getJson() {
            return json;
        }

        public void setJson(String json) {
            this.json = json;
        }

    }
}

y una prueba unitaria

@WebAppConfiguration
@ContextConfiguration(classes = WebConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Example {

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Test
    public void test() throws Exception {

        MockMultipartFile firstFile = new MockMultipartFile("data", "filename.txt", "text/plain", "some xml".getBytes());
        MockMultipartFile secondFile = new MockMultipartFile("data", "other-file-name.data", "text/plain", "some other type".getBytes());
        MockMultipartFile jsonFile = new MockMultipartFile("json", "", "application/json", "{\"json\": \"someValue\"}".getBytes());

        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
        mockMvc.perform(MockMvcRequestBuilders.multipart("/upload")
                        .file(firstFile)
                        .file(secondFile)
                        .file(jsonFile)
                        .param("some-random", "4"))
                    .andExpect(status().is(200))
                    .andExpect(content().string("success"));
    }
}

Y el @Configuration clase

@Configuration
@ComponentScan({ "test.controllers" })
@EnableWebMvc
public class WebConfig extends WebMvcConfigurationSupport {
    @Bean
    public MultipartResolver multipartResolver() {
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
        return multipartResolver;
    }
}

La prueba debe pasar y darle una salida de

4 // from param
someValue // from json file
filename.txt // from first file
other-file-name.data // from second file

Lo que debe tener en cuenta es que está enviando el JSON como cualquier otro archivo de varias partes, excepto con un tipo de contenido diferente.

Sotirios Delimanolis
fuente
1
Hola Sotirios, me alegré de ver ese hermoso ejemplo, y luego miré quién era el que lo ofreció, ¡y bingo! ¡Fue Sotirios! La prueba lo hace realmente genial. Sin embargo, tengo una cosa que me molesta, se queja de que la solicitud no es de varias partes (500).
Stephane
Es esta afirmación la que falla assertIsMultipartRequest (servletRequest); Sospeché que CommonsMultipartResolver no estaba configurado. Pero un registrador en mi bean se muestra en el registro.
Stephane
@shredding Tomé este enfoque al enviar un archivo de varias partes y un objeto de modelo como json a mi controlador. Pero el objeto modelo se lanza MethodArgumentConversionNotSupportedExceptional presionar el controlador ... ¿algún paso pequeño que me haya perdido aquí? - stackoverflow.com/questions/50953227/…
Brian J
1
Este ejemplo me ayudó mucho. Gracias
kiranNswamy
multipart utiliza el método POST. ¿Alguien puede darme este ejemplo pero con el método PATCH?
lalilulelo_1986
16

Eche un vistazo a este ejemplo tomado del escaparate de Spring MVC, este es el enlace al código fuente :

@RunWith(SpringJUnit4ClassRunner.class)
public class FileUploadControllerTests extends AbstractContextControllerTests {

    @Test
    public void readString() throws Exception {

        MockMultipartFile file = new MockMultipartFile("file", "orig", null, "bar".getBytes());

        webAppContextSetup(this.wac).build()
            .perform(fileUpload("/fileupload").file(file))
            .andExpect(model().attribute("message", "File 'orig' uploaded successfully"));
    }

}
Universidad Angular
fuente
1
fileUploadestá en desuso a favor de multipart(String, Object...).
naXa
14

El método MockMvcRequestBuilders.fileUploades de uso obsoletoMockMvcRequestBuilders.multipart .

Esto es un ejemplo:

import static org.hamcrest.CoreMatchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.multipart.MultipartFile;


/**
 * Unit test New Controller.
 *
 */
@RunWith(SpringRunner.class)
@WebMvcTest(NewController.class)
public class NewControllerTest {

    private MockMvc mockMvc;

    @Autowired
    WebApplicationContext wContext;

    @MockBean
    private NewController newController;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(wContext)
                   .alwaysDo(MockMvcResultHandlers.print())
                   .build();
    }

   @Test
    public void test() throws Exception {
       // Mock Request
        MockMultipartFile jsonFile = new MockMultipartFile("test.json", "", "application/json", "{\"key1\": \"value1\"}".getBytes());

        // Mock Response
        NewControllerResponseDto response = new NewControllerDto();
        Mockito.when(newController.postV1(Mockito.any(Integer.class), Mockito.any(MultipartFile.class))).thenReturn(response);

        mockMvc.perform(MockMvcRequestBuilders.multipart("/fileUpload")
                .file("file", jsonFile.getBytes())
                .characterEncoding("UTF-8"))
        .andExpect(status().isOk());

    }

}
Romina Liuzzi
fuente
2

Esto es lo que funcionó para mí, aquí estoy adjuntando un archivo a mi EmailController bajo prueba. También eche un vistazo a la captura de pantalla del cartero sobre cómo estoy publicando los datos.

    @WebAppConfiguration
    @RunWith(SpringRunner.class)
    @SpringBootTest(
            classes = EmailControllerBootApplication.class
        )
    public class SendEmailTest {

        @Autowired
        private WebApplicationContext webApplicationContext;

        @Test
        public void testSend() throws Exception{
            String jsonStr = "{\"to\": [\"[email protected]\"],\"subject\": "
                    + "\"CDM - Spring Boot email service with attachment\","
                    + "\"body\": \"Email body will contain  test results, with screenshot\"}";

            Resource fileResource = new ClassPathResource(
                    "screen-shots/HomePage-attachment.png");

            assertNotNull(fileResource);

            MockMultipartFile firstFile = new MockMultipartFile( 
                       "attachments",fileResource.getFilename(),
                        MediaType.MULTIPART_FORM_DATA_VALUE,
                        fileResource.getInputStream());  
                        assertNotNull(firstFile);


            MockMvc mockMvc = MockMvcBuilders.
                  webAppContextSetup(webApplicationContext).build();

            mockMvc.perform(MockMvcRequestBuilders
                   .multipart("/api/v1/email/send")
                    .file(firstFile)
                    .param("data", jsonStr))
                    .andExpect(status().is(200));
            }
        }

Solicitud de cartero

Alferd Nobel
fuente
Muchas gracias, tu respuesta también funcionó para mí @Alfred
Parameshwar
1

Si está utilizando Spring4 / SpringBoot 1.x, entonces vale la pena mencionar que también puede agregar partes de "texto" (json). Esto se puede hacer a través del archivo MockMvcRequestBuilders.fileUpload (). (Archivo MockMultipartFile) (que es necesario ya que el método .multipart()no está disponible en esta versión):

@Test
public void test() throws Exception {

   mockMvc.perform( 
       MockMvcRequestBuilders.fileUpload("/files")
         // file-part
         .file(makeMultipartFile( "file-part" "some/path/to/file.bin", "application/octet-stream"))
        // text part
         .file(makeMultipartTextPart("json-part", "{ \"foo\" : \"bar\" }", "application/json"))
       .andExpect(status().isOk())));

   }

   private MockMultipartFile(String requestPartName, String filename, 
       String contentType, String pathOnClassPath) {

       return new MockMultipartFile(requestPartName, filename, 
          contentType, readResourceFile(pathOnClasspath);
   }

   // make text-part using MockMultipartFile
   private MockMultipartFile makeMultipartTextPart(String requestPartName, 
       String value, String contentType) throws Exception {

       return new MockMultipartFile(requestPartName, "", contentType,
               value.getBytes(Charset.forName("UTF-8")));   
   }


   private byte[] readResourceFile(String pathOnClassPath) throws Exception {
      return Files.readAllBytes(Paths.get(Thread.currentThread().getContextClassLoader()
         .getResource(pathOnClassPath).toUri()));
   }

}
walkeros
fuente