Testing file upload in Spring Boot leads to FileUploadException (multipart boundary was not set) - spring

I'm trying to upload files to my Spring Boot application and directly writing them to their destination (not in a temp file first). The application code I have works, but I can't get my unit test to work. My controller looks like this:
#PostMapping("/upload")
#ResponseBody
public String handleFileUpload(final HttpServletRequest request) throws IOException {
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (!isMultipart) {
throw new ResponseStatusException(BAD_REQUEST, "Input was not of type multipart");
}
ServletFileUpload upload = new ServletFileUpload();
FileItemIterator fileIterator = upload.getItemIterator(request);
while (fileIterator.hasNext()) {
FileItemStream item = fileIterator.next();
if (!item.isFormField()) {
// Save the file
try {
return myFileStorageService.store(item.openStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
throw new ResponseStatusException(BAD_REQUEST, "Input did not contain a file");
}
This code works great, but my test doesn't:
#MockBean
private MyFileStorageService myFileStorageService;
#Autowired
private MockMvc mockMvc;
#Test
void shouldUploadFile() throws Exception {
final InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("testfile.txt");
final MockMultipartFile testFile = new MockMultipartFile("file", "testfile.txt", null, inputStream);
doReturn("success!").when(myFileStorageService).store(testFile);
mockMvc.perform(multipart("/upload").file(testFile))
.andExpect(status().isOk())
.andExpect(content().string("success!"));
verify(myFileStorageService).store(testFile);
}
This results in the following exception:
org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.init(FileItemIteratorImpl.java:189)
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.getMultiPartStream(FileItemIteratorImpl.java:205)
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.findNextItem(FileItemIteratorImpl.java:224)
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.<init>(FileItemIteratorImpl.java:142)
at org.apache.tomcat.util.http.fileupload.FileUploadBase.getItemIterator(FileUploadBase.java:252)
at org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload.getItemIterator(ServletFileUpload.java:134)
at com.lolmewn.FileUploadController.handleFileUpload(FileUploadController.java:128)
...
And in my config, I have configured the following:
spring:
servlet:
multipart:
enabled: false
max-file-size: -1
max-request-size: -1
I expect Spring would generate the multipart boundaries for me, just like the browser or Postman do, is this not the case? I saw many similar questions, with most of them explicitly setting their content-type as the primary error, but as far as I know I'm not setting a content-type anywhere, so I expect Spring to generate it for me.

If you are using default application.properties, then add #SpringBootTest annotation at top of your class which will instantiate it. If using something like application-test.properties you need to include #ActiveProfiles(test)
as well.
If you are using a config class to represent it
#EnableConfigurationProperties(value = YourConfig.class)
EDIT: Change
final MockMultipartFile testFile = new MockMultipartFile("file", "testfile.txt", null, inputStream);
To
final MockMultipartFile testFile = new MockMultipartFile("file", "testfile.txt",
MediaType.MULTIPART_FORM_DATA_VALUE, inputStream);

Related

Required request part 'file' is not present in Spring Boot

I checked all of the simular posts and still couldnt find the solution.
Problem is Required request part 'file' is not present in test class.
I want to upload a file and save it to the database. Here is my rest controller #RestController:
#PostMapping(value = "/upload")
public ResponseEntity<LogoDto> uploadLogo(#RequestParam("file") MultipartFile multipartFile) {
return ResponseEntity.ok(logoService.createLogo(multipartFile));
}
and my test class:
#Test
public void createLogo2() throws Exception {
String toJsonLogoDto = new Gson().toJson(logoDto);
MockMultipartFile file = new MockMultipartFile("path", "url", MediaType.APPLICATION_JSON_VALUE, image);
LogoDto response = LogoDataTest.validLogoDto();
Mockito.when(logoServiceMock.createLogo(Mockito.any(MultipartFile.class))).thenReturn(response);
mockMvc.perform(MockMvcRequestBuilders.multipart("/brand-icon/upload")
.file(file)
.content(MediaType.APPLICATION_JSON_VALUE)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.characterEncoding(CharEncoding.UTF_8))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk());
}
and my application.yml looks like this:
spring:
servlet:
multipart:
enabled: true
max-file-size: 2MB
max-request-size: 10MB
I tried to add consumes in my #PostMapping;
try to set literally every MediaTypes.. still get an error.
I appreciate all of your answer.
issue is in declaration of MockMultipartFile, first parameter should match controller #RequestParam param. So, in your case, should be:
MockMultipartFile file = new MockMultipartFile("file", "url", MediaType.APPLICATION_JSON_VALUE, image);
Also, I recommend to update your controller method to the following one:
#PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<LogoDto> uploadLogo(#RequestPart("file") MultipartFile multipartFile) {
...
}

MockMvc Test does not get to the endpoint for a Multipart file in a RestController

I am calling a service in an orders controller which receives a multipart file and processes it and saving it into a database. I am trying to create a Spring Rest Doc for it but it is not even hitting the endpoint. I am creating a list of orders which is what the service expects. It receives the order as a stream as shown and converts into a stream of orders before saving it into a database. I have shown the main part of the controller and my code for generating the rest docs. When I run the code I get the following exception, it never even hits the endpoint when I set a breakpoint. I also used fileupload() but that did not work either.
Exception is:
Content type = application/json
Body = {"path":"/orders/order_reception","exceptionName":
"MissingServletRequestPartException","message":"Required request part 'uploadFile' is not
present",
"rootExceptionName":"MissingServletRequestPartException",
"rootMessage":"MissingServletRequestPartException: Required request part 'uploadFile' is not present"}
#RestController
#RequestMapping(value = "/orders")
#Validated
class OrderController{
#PostMapping(path = "/order_reception")
public ResponseEntity receiveData(#RequestPart MultipartFile uploadFile,
HttpServletRequest request,
HttpServletResponse response) {
if (!uploadFile.isEmpty()) {
try {
Reader reader = new InputStreamReader(request.getInputStream()));
... save file
return new ResponseEntity<>(HttpStatus.HttpStatus.CREATED);
} catch (Exception e) {
return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
#Test
public void sendData() throws Exception {
ObjectMapper mapper = new ObjectMapper();
Order order = repository.getOrder("1233333");
List<Order> orderList = new ArrayList<>():
resourceList.add(order);
MockMultipartFile orderFile = new MockMultipartFile("order-data", "order.json", "application/json",
mapper.writeValueAsString(orderList).getBytes(Charset.defaultCharset()));
mockMvc.perform(multipart("/orders/order_reception")
.file(orderFile))
.andExpect(status().isCreated())
.andDo(document("send-order",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint())));
}
Thank you Marten Deinum, your suggestion that the file name was wrong fixed it.
I simply changed name in the MockMultipartFile( "uploadsFile", ...)

How to use MockMVC test the controller which use org.apache.commons.fileupload?

My Controller use " org.apache.commons.fileupload " realized the file UPload.
see it:
#PostMapping("/upload")
public String upload2(HttpServletRequest request) throws Exception {
ServletFileUpload upload = new ServletFileUpload();
FileItemIterator iter = upload.getItemIterator(request);
boolean uploaded = false;
while (iter.hasNext() && !uploaded) {
FileItemStream item = iter.next();
if (item.isFormField()) {
item.openStream().close();
} else {
String fieldName = item.getFieldName();
if (!"file".equals(fieldName)) {
item.openStream().close();
} else {
InputStream stream = item.openStream();
// dosomething here.
uploaded = true;
}
}
}
if (uploaded) {
return "ok";
} else {
throw new BaseResponseException(HttpStatus.BAD_REQUEST, "400", "no file field or data file is empty.");
}
}
and my MockMvc code is
public void upload() throws Exception {
File file = new File("/Users/jianxiaowen/Documents/a.txt");
MockMultipartFile multipartFile = new MockMultipartFile("file", new FileInputStream(file));
HashMap<String, String> contentTypeParams = new HashMap<String, String>();
contentTypeParams.put("boundary", "----WebKitFormBoundaryaDEFKSFMY18ehkjt");
MediaType mediaType = new MediaType("multipart", "form-data", contentTypeParams);
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post(baseUrl+"/upload")
.content(multipartFile.getBytes())
.contentType(mediaType)
.header(Origin,OriginValue)
.cookie(cookie))
.andReturn();
logResult(mvcResult);
}
my controller is right , it has successed in my web project,
but I want to test it use MvcMock, it has some mistake, see :
can someOne can help me?
"status":"400","msg":"no file field or data file is empty.","data":null
I don't know why it says my file is empty.
my English is poor, thank you very much if someone can help me.
The MockMvc can be used for integration testing for controllers using Apache Commons Fileupload too!
Import the org.apache.httpcomponents:httpmime into your pom.xml or gradle.properties
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.13</version>
</dependency>
Update the code to use MultipartEntityBuilder to build the multipart request on the client, and then serialize the entity into bytes, which is then set in the request content
public void upload() throws Exception {
File file = new File("/Users/jianxiaowen/Documents/a.txt");
String boundary = "----WebKitFormBoundaryaDEFKSFMY18ehkjt";
// create 'Content-Type' header for multipart along with boundary
HashMap<String, String> contentTypeParams = new HashMap<String, String>();
contentTypeParams.put("boundary", boundary); // set boundary in the header
MediaType mediaType = new MediaType("multipart", "form-data", contentTypeParams);
// create a multipart entity builder, and add parts (file/form data)
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
HttpEntity multipartEntity = MultipartEntityBuilder.create()
.addPart("file", new FileBody(file, ContentType.create("text/plain"), file.getName())) // add file
// .addTextBody("param1", "value1") // optionally add form data
.setBoundary(boundary) // set boundary to be used
.build();
multipartEntity.writeTo(outputStream); // or getContent() to get content stream
byte[] content = outputStream.toByteArray(); // serialize the content to bytes
MvcResult mvcResult = mockMvc.perform(
MockMvcRequestBuilders.post(baseUrl + "/upload")
.contentType(mediaType)
.content(content) // finally set the content
.header(Origin,OriginValue)
.cookie(cookie)
).andReturn();
logResult(mvcResult);
}
Can you try the below?
mockMvc.perform(
MockMvcRequestBuilders.multipart(baseUrl+"/upload")
.file(multiPartFile)
).andReturn();
Update:
You need to update the controller to handle the MultipartFile:
#PostMapping("/upload")
public String upload2(#RequestParam(name="nameOfRequestParamWhichContainsFileData")
MultipartFile uploadedFile, HttpServletRequest request) throws Exception {
//the uploaded file gets copied to uploadedFile object.
}
You need not use another library for managing file uploads. You can use the file upload capabilities provided by Spring MVC.

Testing a Post multipart/form-data request on REST Controller

I've written a typical spring boot application, now I want to add integration tests to that application.
I've got the following controller and test:
Controller:
#RestController
public class PictureController {
#RequestMapping(value = "/uploadpicture", method = RequestMethod.POST)
public ResponseEntity<VehicleRegistrationData> uploadPicturePost(#RequestPart("userId") String userId, #RequestPart("file") MultipartFile file) {
try {
return ResponseEntity.ok(sPicture.saveAndParsePicture(userId, file));
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
}
Test:
#Test
public void authorizedGetRequest() throws Exception {
File data = ResourceUtils.getFile(testImageResource);
byte[] bytes = FileUtils.readFileToByteArray(data);
ObjectMapper objectMapper = new ObjectMapper();
MockMultipartFile file = new MockMultipartFile("file", "test.jpg", MediaType.IMAGE_JPEG_VALUE, bytes);
MockMultipartFile userId =
new MockMultipartFile("userId",
"userId",
MediaType.MULTIPART_FORM_DATA_VALUE,
objectMapper.writeValueAsString("123456").getBytes()
);
this.mockMvc.perform(multipart("/uploadPicture")
.file(userId)
.file(file)
.header(API_KEY_HEADER, API_KEY)).andExpect(status().isOk());
}
Testing the controller with the OkHttp3 client on android works seamlessly, but I can't figure out how to make that request work on the MockMvc
I expect 200 as a status code, but get 404 since, I guess, the format is not the correct one for that controller
What am I doing wrong?
It must be a typo.
In your controller, you claim the request URL to be /uploadpicture, but you visit /uploadPicture for unit test.

Using multiple template resolvers with Spring 3.2 and Thymeleaf 2.1.3 for emails

I have problem defining a ClassLoaderTemplateResolver for emails and one ServletContextTemplateResolver for web views. I getting the following error when trying to send emails:
HTTP Status 500 - Request processing failed; nested exception is
org.thymeleaf.exceptions.TemplateProcessingException: Resource resolution by ServletContext with
org.thymeleaf.resourceresolver.ServletContextResourceResolver can only be performed when context
implements org.thymeleaf.context.IWebContext [current context: org.thymeleaf.context.Context]
My WebMvcConfig looks like this:
private static final String VIEWS_PATH = "/WEB-INF/views/";
private static final String MAIL_PATH = "mail/";
#Bean
public ServletContextTemplateResolver templateResolver() {
final ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
resolver.setPrefix(VIEWS_PATH);
resolver.setSuffix(".html");
resolver.setTemplateMode("HTML5");
resolver.setCharacterEncoding("UTF-8");
resolver.setOrder(2);
resolver.setCacheable(false);
return resolver;
}
#Bean
public ClassLoaderTemplateResolver emailTemplateResolver() {
final ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
resolver.setPrefix(MAIL_PATH);
resolver.setSuffix(".html");
resolver.setTemplateMode("HTML5");
resolver.setCharacterEncoding("UTF-8");
resolver.setOrder(1);
return resolver;
}
#Bean
public SpringTemplateEngine templateEngine() {
final SpringTemplateEngine engine = new SpringTemplateEngine();
final Set<TemplateResolver> templateResolvers = new HashSet<TemplateResolver>();
templateResolvers.add(templateResolver());
templateResolvers.add(emailTemplateResolver());
engine.setTemplateResolvers(templateResolvers);
engine.addDialect(new SpringSocialDialect());
engine.addDialect(new SpringSecurityDialect());
return engine;
}
And my EmailService like this:
#Service
public class EmailService {
#Autowired
private JavaMailSender mailSender;
#Autowired
private TemplateEngine templateEngine;
/*
* Send HTML mail with inline image
*/
public void sendEmailToBookSeller(
final ContactBookSellerForm form,
final Locale locale) throws MessagingException {
boolean multipart = true;
boolean isHtml = true;
// Prepare the evaluation context
final Context ctx = new Context(locale);
ctx.setVariable("message", form.getMessage());
ctx.setVariable("bookTitle", form.getBookTitle());
ctx.setVariable("email", form.getToEmail());
ctx.setVariable("logo", "logo");
ctx.setVariable("logoOnlyText", "logoOnlyText");
// Prepare message
final MimeMessage mimeMessage = mailSender.createMimeMessage();
final MimeMessageHelper message = new MimeMessageHelper(mimeMessage, multipart, "UTF-8");
message.setSubject("Regarding your book on Mimswell - " + form.getBookTitle());
message.setFrom(form.getFromEmail());
message.setTo(form.getToEmail());
// Create the HTML body using Thymeleaf
final String htmlContent = templateEngine.process("email-buy-book.html", ctx);
message.setText(htmlContent, isHtml);
message.addInline("logo", new ClassPathResource("WEB-INF/views/mail/logo130130red.png"), "image/png");
message.addInline("logoOnlyText", new ClassPathResource("WEB-INF/views/mail/logo_only_text.png"), "image/png");
// Send mail
this.mailSender.send(mimeMessage);
}
}
The error occours on the following line:
final String htmlContent = templateEngine.process("email-buy-book.html", ctx);
Where it is using ServletContextResourceResolver instead of my other resolver. I want it to use ClassLoaderTemplateResolver since it can handle plain Context objects instead of having to use WebContext. However, I could try to use a WebContext instead since it implements the IWebContext and only use one resolver. But then I need a HttpServletRequest, HttpServletResponse and a ServletContext as parameters which seems to messy.
My structure :
Any idea whats wrong in my code?
I gave up this and went for the WebContext approach instead, even though i'm stuck needing the request, response and servletcontext every time sending something. This is how I did it:
1. Get the servlet context:
#Autowired
ServletContext servletContext;
2. Get the request and response as parameters to the sendmail method:
HttpServletRequest request,
HttpServletResponse response
3. Create the WebContext instead:
final WebContext ctx = new WebContext(request, response, servletContext, locale);
It worked from now on.
Since you (correctly) set the ClassLoaderTemplateResolver to have priority over the ServletContextTemplateResolver, Thymeleaf tries to use the correct order but fails to resolve the view with former and then tries latter.
I believe that the problem is with the prefix and suffix parameters you set combined with the view name you pass to templateEngine.process method. Thymeleaf will construct your view name by concatenating suffix + viewname + suffix resulting to "mail/email-buy-book.html.html".
Try to pass only "email-buy-book" and see if it solves the problem.
Since you're using the ClassLoaderTemplateResolver, Spring is going to use the prefix and append it to WEB-INF/classes. So the thing to check is whether Maven (or whatever build tool you're using) copied the html file to WEB-INF/classes/mail/email-buy-book.html. If it didn't, try copying it manually and give it a go. Looking at your screenshot, I don't see the "mail" folder under "classes" so this could be the issue.
Also, only pass "email-buy-book" and leave out the extension as #grid mentioned.
final String htmlContent = templateEngine.process("email-buy-book", ctx);
I have it working with XML config and not Java config, but I don't see why that should matter for you.

Resources