Spring MVC test Mulitple file upload - java

I have controller, which handles multiple file upload:
#PostMapping("/import")
public void import(#RequestParam("files") MultipartFile[] files, HttpServletRequest request) {
assertUploadFilesNotEmpty(files);
...
}
And I want to test it
#Test
public void importTest() throws Exception {
MockMultipartFile file = new MockMultipartFile("file", "list.xlsx", MIME_TYPE_EXCEL, Files.readAllBytes(Paths.get(excelFile.getURI())));
mvc.perform(fileUpload("/import").file(file).contentType(MIME_TYPE_EXCEL)).andExpect(status().isOk());
}
Problem is that MockMvc, creates MockHttpRequest with multipartFiles as a name for param that holds uploaded files. And my controller expects those files will be in 'files' param.
Is it possible to tell spring that multiple files should be passed in request under given name?

Create two MockMultiPartFile instances with name files
Complete working example with added Json request body as well as multiple files below :
#PostMapping(consumes=MediaType.MULTIPART_FORM_DATA_VALUE)
public void addProduct(#RequestPart(value="addProductRequest") #Valid AddUpdateProductRequest request,
#RequestPart(value = "files") final List<MultipartFile> files) throws Exception{
request.setProductImages(files);
productService.createProduct(request);
}
#Test
public void testUpdateProduct() throws Exception {
AddUpdateProductRequest addProductRequest = prepareAddUpdateRequest();
final InputStream inputStreamFirstImage = Thread.currentThread().getContextClassLoader().getResourceAsStream("test_image.png");
final InputStream inputStreamSecondImage = Thread.currentThread().getContextClassLoader().getResourceAsStream("test_image2.png");
MockMultipartFile jsonBody = new MockMultipartFile("addProductRequest", "", "application/json", JsonUtils.toJson(addProductRequest).getBytes());
MockMultipartFile file1 = new MockMultipartFile("files", "test_image.png", "image/png", inputStreamFirstImage);
MockMultipartFile file2 = new MockMultipartFile("files", "test_image2.png", "image/png", inputStreamSecondImage);
ResultMatcher ok = MockMvcResultMatchers.status().isOk();
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/add-product")
.file(file1)
.file(file2)
.file(jsonBody)
.contentType(MediaType.MULTIPART_FORM_DATA_VALUE))
.andDo(MockMvcResultHandlers.log())
.andExpect(ok)
.andExpect(content().string("success"));
}

Related

getting 400 badrequest while passing multipart file in request in Junit

when I am trying to write the junit for my controller class, I am getting 400 bad request.
In console , i can see below error:
logException Resolved [org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present
this is my Junit
#Test
void testExcelFile() throws Exception {
FileInputStream inputFile = new FileInputStream("path of the file");
MockMultipartFile file = new MockMultipartFile("file", "myexcelFile.xlsx", "multipart/form-data", inputFile);
Mockito.when(service.upload((MultipartFile) file)).thenReturn("received");
RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/uploadData",file)
.param("file",file.getName()).contentType(MediaType.MULTIPART_FORM_DATA);
MvcResult result =
mockMvc.perform(requestBuilder).andExpect(status().isAccepted()).andReturn();
assertEquals(HttpStatus.ACCEPTED.value(), result.getResponse().getStatus());
}
This is my controller class
#PostMapping("/uploadData")
public ResponseEntity<String> save(#RequestParam(required = true) MultipartFile file) {
if(ExcelHelper.checkExcelFormat(file)) {
return new ResponseEntity<>(service.upload(file),HttpStatus.ACCEPTED);
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Please upload excel file only");
}
Please help what I am doing wrong. Thanks in advance.

How can I test this springboot controller method with multipart and a object from a form submit?

I have the following controller method
#PostMapping("/create")
public ModelAndView createDocument(#ModelAttribute("user") #Valid User userToSave, BindingResult bindingResult,#RequestParam String adressId,#RequestParam MultipartFile file){
Now I want to create a integration test which supplies the following parameters. But I always get a 403. My testmethod looks like that:
#Test
#WithMockUser(username = "user",password = "user123",roles="ADMIN")
public void testUploadDocument() throws Exception {
User mockUser = new User("User",null,null,"test comment","Dies ist ein Test");
MockMultipartFile file
= new MockMultipartFile(
"file",
"hello.txt",
MediaType.TEXT_PLAIN_VALUE,
"Hello, World!".getBytes()
);
this.mockMvc.perform(MockMvcRequestBuilders.multipart("/create").file(file).param("adressId", "96cf9ec3-f820-4192-a4db-920328df5ff4")
.param("file", objectMapper.writeValueAsString(mockUser))
).andDo(print()).andExpect(status().isOk())
.andExpect(view().name(containsString("list")));
}

Spring Test Controller upload Json and Multipart File Always return 405

I want to test my Spring Controller that has input json and multipart file, however even if the function works, I can't get the test works
I tried using MockMvcRequestBuilders.fileUpload but the test always get 405
The controller:
#PutMapping(value = "/upload", consumes = {"multipart/form-data"})
public BaseResponse<SomeModel> updateSomeModelContent(
#ApiIgnore #Valid #ModelAttribute MandatoryRequest mandatoryRequest,
#PathVariable("id") String id,
#RequestParam("file") MultipartFile file,
#RequestParam("json") String json) throws IOException {
final CommonSomeModelRequest request = JSONHelper.convertJsonInStringToObject(json, CommonSomeModelRequest.class);
return makeResponse(someModelSer.updateContent(id, request, mandatoryRequest, file));
}
The test:
#Test
public void updateCountryContentSuccessTest() throws Exception {
MockMultipartFile file1 = new MockMultipartFile("file", "filename-1.jpeg", "image/jpeg", "some-image".getBytes());
MockMultipartFile file2 = new MockMultipartFile("json", "", "application/json","{\"exampleAttr\": \"someValue\"}".getBytes());
when(this.someModelService
.updateContent(id, request, MANDATORY_REQUEST, file1))
.thenReturn(someModelUpdatedContent);
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders
.fileUpload("/upload/{id}", id)
.file(file1)
.file(file2)
.requestAttr("mandatory", MANDATORY_REQUEST);
this.mockMvc.perform(builder)
.andExpect(status().isOk());
verify(this.someModelService)
.updateContent(id, request, MANDATORY_REQUEST, file1);
}
The result status is always 405, I don't know how to make it 200
MockMvcRequestBuilders.multipart(...) create MockMultipartHttpServletRequestBuilder which support only POST.
But in your controller you use POST. You should change PUT to POST mapping in the rest controller.
Also according to RFCs we should use POST instead of PUT on multipart upload.
Have a look at Spring MVC Framework: MultipartResolver with PUT method

Empty List<MultipartFile> when trying to upload many files in Spring with ng-file-upload

I have the following controller method for uploading multiple files at once, inspired by this blog post and answers to this question as well:
#RequestMapping(value = "/{user}/attachment", method = RequestMethod.POST)
#PreAuthorize(...)
public void upload(#PathVariable User user,
#RequestParam("file") List<MultipartFile> files) {
// handle files
}
However, the list of the files is always empty although request contains them.
If I add the third MultipartRequest parameter to the method:
public void upload(#PathVariable User user,
#RequestParam("file") List<MultipartFile> files,
MultipartRequest request)
I can see it contains my uploaded files correctly:
What might be the reason of empty List<MultipartFile>?
I'm using ng-file-upload to submit the files, but I don't think it is connected with the issue. Spring 4.2.4.
The problem was that ng-file-upload by default submits array of files using names file[0], file[1] etc. It is configurable with the arrayKey value when using Upload Service. Setting it to empty string forces the files to be sent under the same file key, which is correctly resolved with Spring and the #RequestParam("file") List<MultipartFile> contains all files that has been submitted.
Upload.upload({url: url, data: {file: arrayOfFiles}, arrayKey: ''})
Try to use #ModelAttribute like this:
#RequestMapping(value = "/{user}/attachment", method = RequestMethod.POST)
#PreAuthorize(...)
public void upload(#PathVariable User user,#ModelAttribute("uploadFile") FileUpload uploadFile) throws IllegalStateException, IOException {
List<MultipartFile> files = uploadFile.getFiles();
...
And create a class like:
public class FileUpload {
private List<MultipartFile> files;
public List<MultipartFile> getFiles() {
return files;
}
public void setFiles(List<MultipartFile> files) {
this.files= files;
}
}
That works for me, sending big 'email' object with multiple file attachments from UI to back-end:
Angular
sendEmailWithAttachments(taskId: string, template: string, email: any, modelConfig: any, files: any[]) {
let formData = new FormData();
formData.append('form', new Blob([JSON.stringify(email)], {type: 'application/json'}));
files.forEach(file => {
formData.append('files', file);
});
return this.$http({
method: 'POST',
data: formData,
url: this.baseUrl + '/' + taskId + '/email-with-attachment?template=' + template,
headers: {
'Content-Type': undefined
},
responseType: 'arraybuffer'
});
}
Java Spring
#RequestMapping(value = "{taskId}/email-with-attachment", method = RequestMethod.POST, consumes = MULTIPART_FORM_DATA_VALUE)
public void sendEmailWithAttachment(
#PathVariable String taskId,
#RequestParam String template,
#RequestParam("form") MultipartFile form,
#RequestParam("files") List<MultipartFile> files) throws IOException {
Map<String, String> parameters = new ObjectMapper().readValue(form.getInputStream(), HashMap.class);
System.out.println("taskId"+ taskId);
System.out.println("template"+ template);
System.out.println("files"+ files);
System.out.println("parameters"+ parameters);
}
for multiple files. do this in your javascript
//first add files to form data
var formData = new FormData();
for (let i = 0; i < files.length; i++) {
formData.append("images", files[i]);
}
//post files to backend e.g using angular
$http.post('upload', formData, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
})
.then(function(response){
console.log("UPLOAD COMPLETE::=> ", response);
}, function (error) {
console.log(error);
});
Do this in your java
//your java method signature
#PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE )
public Response uploadImage(#RequestParam(value = "images") MultipartFile[] images){
}
I think that in the way you sent data from front, it can not bound with java.util.List. If you create a JSON data as request and you annotated your List with #RequestBody like:
#RequestMapping(value = "/{user}/attachment", method = RequestMethod.POST)
#PreAuthorize(...)
public void upload(#PathVariable User user,
#RequestBody List<MultipartFile> files) {
// handle files
}
this should work. Some info here.

How to redirect file to another spring controller?

I have the following controller method:
#RequestMapping(value = { "/member/uploadExternalImage",
"/member/uploadExternalImage" }, method = RequestMethod.GET)
public String handleFileUpload(#RequestParam String url, RedirectAttributes redirectAttributes
) throws IOException {
byte[] binaryFile = IOUtils.toByteArray(
new URL(url)
.openStream());
File file = File.createTempFile("tmp", ".txt", new File(System.getProperty("user.dir")));
FileUtils.writeByteArrayToFile(file, binaryFile);
redirectAttributes.addFlashAttribute(file);
return "redirect:/member/uploadImage";
}
Here I get external link, download file by this link and redirect it to the another controller:
It looks like this:
#RequestMapping(value = { "/member/createCompany/uploadImage",
"/member/uploadImage" })
#ResponseBody
public ResponseEntity<String> handleFileUpload(#Validated MultipartFileWrapper file,
BindingResult result, Principal principal) throws IOException {
MultipartFileWrapper:
#Component
public class MultipartFileWrapper {
#Extensions(imageFormats = {".jpg",".png",".gif",".bmp"}, videoFormats = {".mp4",".mov"})
MultipartFile multipartFile;
...
}
But redirect doesn't happen properly. It breaks on validation. Accepted multipartFile is null.
How to fix it ?
P.S.
I tryed this
File file = File.createTempFile("tmp", ".jpg", new File(System.getProperty("user.dir")));
FileUtils.writeByteArrayToFile(file, binaryFile);
FileItem fileItem = new DiskFileItem("trololo", ".jpg", false, "fileName", 1024_000_0, file);
fileItem.getOutputStream();
fileItem.getInputStream();
MultipartFile multipartFile = new CommonsMultipartFile(fileItem);
MultipartFileWrapper wrapper = new MultipartFileWrapper();
wrapper.setMultipartFile(multipartFile);
redirectAttributes.addFlashAttribute(wrapper);
return "redirect:/member/uploadImage";
it redirects correctly but size equals 0
You add a File object as a flash attribute. So you will get it in the Model for the redirected request. But I cannot imagine how you could get it in a MultipartFile which is for uploaded files. IMHO your second controller should be:
#RequestMapping(value = { "/member/createCompany/uploadImage",
"/member/uploadImage" })
#ResponseBody
public ResponseEntity<String> handleFileUpload(Model model, Principal principal) throws IOException {
File file = (File) model.getAttribute("file");
...
As shown the code snippet below, just read the file from input stream and write it into the output stream,
final File TEST_FILE = new File("C:/Users/arrows.gif");
final DiskFileItem diskFileItem = new DiskFileItem("file", "image/jpeg", true, TEST_FILE.getName(), 100000000, TEST_FILE.getParentFile());
InputStream input = new FileInputStream(TEST_FILE);
OutputStream os = diskFileItem.getOutputStream();
int ret = input.read();
while ( ret != -1 )
{
os.write(ret);
ret = input.read();
}
os.flush();
MultipartFile multipartFile = new CommonsMultipartFile(diskFileItem);
redirectAttributes.addFlashAttribute("multipartFile", multipartFile);
return "redirect:request2";
In the "request2" mapping method, just get it from model map,
I hope it should resolve the issue.
I believe the multipartFile is null because it does not exist in the request. You add a file attribute to the redirect attributes, but that is not going to be bound to the MultipartFileWrapper.
Try wrapping your file in a CommonsMultipartFile or MockMultipartFile before redirecting. This was clearly a bad advice since this is no multiform request, and no binding will take place.
The best thing to do would be to handle the file content directly or create a separate method where you handle the file content either it comes from external download or user upload. Then you can add the file as a flash attribute and redirect to this method from both your handleFileUpload methods.
In the common method you will have to pick up the file instance from the model. (like described by Serge Ballesta)

Categories

Resources