Integration test case and file upload - spring

I wrote some code for related to upload a file using spring, It works fine, Now I am writing integration test cases for that but I am facing some issue
My controller method,
#RequestMapping(value = "/{attributeName}/upload", method = RequestMethod.POST)
#ResponseBody
public Result uploadCompany(HttpServletRequest request,
#RequestParam MultipartFile file, #PathVariable String attributeName,
#RequestParam long dateKey)
throws IOException, PromotionException {
some code
}
Test cases
#Test
public void shouldReturnTrueStatusWhenUploadCompany() throws Exception {
MockMultipartFile file = new MockMultipartFile("company_upload", "company_upload.csv",
MediaType.MULTIPART_FORM_DATA_VALUE, EMPLOYEE_NUMBER_FILE_CONTENT.getBytes(UTF_8));
mockMvc.perform(
MockMvcRequestBuilders.fileUpload(
PROMOTION + StringUtils.replace(ATTRIBUTE_NAME, "{attributeName}", "COMPANY") + "/upload")
.file(file).param("dateKey", "852017") .contentType(MediaType.MULTIPART_FORM_DATA)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
But I am getting
2017-05-09 13:42:42,506 ERROR [Test worker] INTERNAL_SERVER_ERROR:
org.springframework.web.bind.MissingServletRequestParameterException: Required MultipartFile parameter 'file' is not present
Where am I wrong?

change your line
MockMultipartFile file = new MockMultipartFile("company_upload", "company_upload.csv",
MediaType.MULTIPART_FORM_DATA_VALUE, EMPLOYEE_NUMBER_FILE_CONTENT.getBytes(UTF_8));
to
MockMultipartFile file = new MockMultipartFile("file", "company_upload.csv",
MediaType.MULTIPART_FORM_DATA_VALUE, EMPLOYEE_NUMBER_FILE_CONTENT.getBytes(UTF_8));
or change your controller method declaration to something like this
public Result uploadCompany(HttpServletRequest request,
#RequestParam(value = "company_upload") MultipartFile file, #PathVariable String attributeName,
#RequestParam long dateKey)

Related

Spring cloud feign not upload video file

I have 2 microservices. The first service(video converter) and the second service(integration Spring with Amazon S3 to store video and image on the Amazon S3).
The first service produces files by schedule and after converts File to MultipartFile uploads it to the second service and after to Amazon S3.
But I stack on the issue with Feigh uploading the video.
When I debug, I saw that the file in #RequestPart in the second service is null. But converting was done successfully.
I try adding encoding as in that post File upload spring cloud feign client, but that does not help.
Code sample first microservice:
Upload methods:
#Override
public Response<String> uploadFile(final Object object, final MultipartFile multipartFile) {
final Map<String, Object> s3Url = videoServiceFeign.uploadFile(multipartFile,
object.getId().toString(), object.getIdIds().toString(), object.getName());
object.setS3Url(s3Url.get("object").toString());
return generateSuccessResponse();
}
#Override
public Response<String> uploadFile(final Stream stream, final File file) throws IOException {
final InputStream inputStream = Files.newInputStream(file.toPath());
final MultipartFile multipartFile = new MockMultipartFile(file.getName(), file.getName(),
ContentType.MULTIPART_FORM_DATA.toString(), IOUtils.toByteArray(inputStream));
return this.uploadFile(stream, multipartFile);
}
Feign client:
#FeignClient(name = "video-service", url = "${video-service.ribbon.listOfServers}")
public interface VideoServiceFeign {
#PostMapping(path = "/api/v1/video/upload", consumes = {"multipart/form-data"})
Map<String, Object> uploadFile(#RequestPart("file") MultipartFile multipartFile,
#RequestParam("id") String id,
#RequestParam("ids") String ids,
#RequestParam("name") String name);
}
Upload endpoint in the second service:
#ApiOperation("Upload new video file")
#ApiResponses({
#ApiResponse(code = 200, message = "File uploaded successfully"),
#ApiResponse(code = 403, message = "Access Denied"),
#ApiResponse(code = 500, message = "Internal server error"),
#ApiResponse(code = 503, message = "Gateway timeout")
})
#ResponseStatus(HttpStatus.OK)
#PostMapping(value = "/upload", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE})
public #ResponseBody
Map<String, Object> uploadFile(#RequestPart("file") final MultipartFile file,
#RequestParam("id") final String id,
#RequestParam("ids") final String ids,
#RequestParam("name") final String name
) throws IOException {
return uploadService.uploadFile(id, ids, name, file);
}
What am I missing?
After a few days of researching, I found what I miss, the issue was with that method: public Response<String> uploadFile(final Stream stream, final File file) throws IOException
In the row:
final MultipartFile multipartFile = new MockMultipartFile(file.getName(), file.getName(), ContentType.MULTIPART_FORM_DATA.toString(), IOUtils.toByteArray(inputStream));
I had two issues first is the content type, I use video/mp4 not ContentType.MULTIPART_FORM_DATA.
And the second one is the first param in the constructor of MockMultipartFile I use the file.getName(), but that is incorrect because the docs say that is a field name in the multipart request. And that should be "file".

Junit test for #RequestPart

Here I want to write a test for following controller method for a spring-boot application, on executing method of the test class I'm getting an error Bad Request: Required request part 'formData' is not present, I've tried finding a solution for testing an object having 'RequestPart' annotation but no luck
//method needs to be tested
#PostMapping("/user")
public ResponseEntity<UserDTO> createUser(#RequestPart(value = "files", required= false) MultipartFile[] files,
#RequestPart("formData") UserDTO userDTO) throws URISyntaxException {
userService.save(userDTO, files);
}
// Inside testclass
#BeforeEach
public void initTest()
{
user = createEntity(em);// inside this method I've set all the properties of user object
}
public void createUser() throws Exception {
UserDTO userDTO = userMapper.toDto(user);
mockMvc
.perform(post("/api/user")
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(userDTO)))
.andExpect(status().isCreated());
}
You have to build a multipart request using MockMultipartFile with something like this:
MockMultipartFile api = new MockMultipartFile (
"file",
"foo.zip",
"application/zip", "foo".bytes)
and then use it like this:
mvc.perform (
multipart ('/api/foo')
.file (api))
.andExpect(...)
This is for a file upload (groovy code), so you have to adjust it a little bit.

POST Request to upload multipart file in Spring Boot

I'm using spring boot, I need to upload a multipart file (jpg or png file). I need to send a (POST request to upload the multi part file using "postman"), can anyone provide a screen shot of "postman" of how to set it up to do that or tell me? Thanks.
method :
#RequestMapping(method = RequestMethod.POST, value = "/upload")
#ResponseBody
ResponseEntity<?> writeUserProfilePhoto(#PathVariable Long user, #RequestPart("file") MultipartFile file) throws Throwable {
byte bytesForProfilePhoto[] = FileCopyUtils.copyToByteArray(file.getInputStream()); //Return an InputStream to read the contents of the file from.
this.crmService.writeUserProfilePhoto(user, MediaType.parseMediaType(file.getContentType()),bytesForProfilePhoto);
HttpHeaders httpHeaders = new HttpHeaders();
URI uriOfPhoto = ServletUriComponentsBuilder.fromCurrentContextPath()
.pathSegment(("/users" + "/{user}" + "/photo").substring(1))
.buildAndExpand(Collections.singletonMap("user", user)).toUri();
httpHeaders.setLocation(uriOfPhoto);
return new ResponseEntity<>(httpHeaders, HttpStatus.CREATED);
}
and this is how I sent the POST request:
my configuration class:
#Configuration
#ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
#ConditionalOnProperty(prefix = "multipart", name = "enabled", matchIfMissing = true)
#EnableConfigurationProperties(MultipartProperties.class)
public class MultipartAutoConfiguration {
#Autowired
private MultipartProperties multipartProperties = new MultipartProperties();
#Bean
#ConditionalOnMissingBean
public MultipartConfigElement multipartConfigElement() {
return this.multipartProperties.createMultipartConfig();
}
#Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
#ConditionalOnMissingBean(MultipartResolver.class)
public StandardServletMultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
}
The error in postman says
Required MultipartFile parameter 'file' is not present
The method signature looks fine defining file parameter:
ResponseEntity<?> writeUserProfilePhoto(
#PathVariable Long user, #RequestPart("file") MultipartFile file)
throws Throwable
The issue is that when using postman, you're using dog1 as the name of this parameter. Change it to file to match the expected parameter name for the multipart file.
This approach worked for me.
The error in postman says
Required MultipartFile parameter 'file' is not present
The method signature looks fine defining file parameter:
ResponseEntity<?> writeUserProfilePhoto(
#PathVariable Long user, #RequestPart("file") MultipartFile file)
throws Throwable
The issue is that when using postman, you're using dog1 as the name of this parameter. Change it to file to match the expected parameter name for the multipart file.
#Override
public Response uploadImage(String token, MultipartFile file) {
long id=tokenUtil.decodeToken(token);
Optional<User> user=userRepo.findById(id);
if(!user.isPresent()) {
throw new UserException(-5, "User does not exists");
}
UUID uuid=UUID.randomUUID();
String uniqueUserId=uuid.toString();
try {
Files.copy(file.getInputStream(), fileLocation.resolve(uniqueUserId), StandardCopyOption.REPLACE_EXISTING);
user.get().setProfilePic(uniqueUserId);
userRepo.save(user.get());
}catch (IOException e) {
e.printStackTrace();
// TODO: handle exception
}
return ResponseHelper.statusResponse(200, "Profile Pic Uploaded Successfully");
}

Different encoding of an HTTP request result, depending on the Accept header

I have a controller with a method to upload files, using, on the client side, the dojo Uploader class that supports ajax uploads for all browsers except IE, and uploads with an IFrame for IE.
The result is a JSON object, but when the IFrame mechanism is used, the JSON must be enclosed in a <textarea>:
#RequestMapping(value = "/documentation/{appId:.+}/", method = RequestMethod.POST)
#ResponseBody
public String uploadDocumentation(HttpServletRequest request,
#PathVariable String appId, #RequestParam("uploadedfile") MultipartFile file)
throws Exception {
// ....
String json = JsonUtils.jsonify(map);
if (accepts(request, "application/json")) {
return json;
} else if (accepts(request, "text/html")) {
return "<textarea>" + json + "</textarea>";
} else {
throw new GinaException("Type de retour non supporté");
}
I was wondering if there is a way to register this encoding mechanism in the framework, so that we would just have to return an object, and let the framework do the rest.
Thanks in advance.
For the record, I simply added a second method:
#RequestMapping(value = "/documentation/{appId:.+}/", method = RequestMethod.POST,
produces="application/json")
#ResponseBody
public UploadResult uploadDocumentation(#PathVariable String appId,
#RequestParam("uploadedfile") MultipartFile file) throws Exception {
...
return new UploadResult(filename);
}
#RequestMapping(value = "/documentation/{appId:.+}/", method = RequestMethod.POST,
produces="text/html")
#ResponseBody
public String uploadDocumentationIE(#PathVariable String appId,
#RequestParam("uploadedfile") MultipartFile file) throws Exception {
UploadResult obj = uploadDocumentation(appId, file);
String json = JsonUtils.jsonify(obj);
return "<textarea>" + json + "</textarea>";
}

MocMVC giving HttpMessageNotReadableException

I'm still learning my way around testing and I'm trying to get a MockMvc test to work for me. It's a simple REST controller that at this point is only doing some authentication using information from json in the post. I've actually implemented the code, so I know it's working because I get back both the correct response with the correct input and the error messages I've put together, both in a json format. My problem is that the test keeps failing with a HttpMessageNotReadableException, even though the actual code works, so I'm assuming I don't have my test set up right. Any help you guys can give would be great.
Here's my controller
#Controller
public class RequestPaymentController {
protected final Log logger = LogFactory.getLog(getClass());
private PaymentService paymentService;
private LoginService loginService;
#Autowired
public void setPaymentService(PaymentService paymentService){
this.paymentService = paymentService;
}
#Autowired
public void setLoginService(LoginService loginService){
this.loginService = loginService;
}
#RequestMapping(value = "/requestpayment", method = RequestMethod.POST, headers="Accept=application/json")
#ResponseBody
public ResponseEntity<PaymentResult> handleRequestPayment(#RequestBody PaymentRequest paymentRequest, HttpServletRequest request, HttpServletResponse response, BindingResult result) throws Exception{
ResponseEntity<PaymentResult> responseEntity = null;
new LoginValidator().validate(paymentRequest, result);
boolean valid = loginService.isLoginValid(paymentRequest, result);
if (valid){
responseEntity = setValidResponse(paymentRequest);
}else {
throw new TumsException("exception message");
}
return responseEntity;
}
private ResponseEntity<PaymentResult> setValidResponse(PaymentRequest paymentRequest){
PaymentResult paymentResult = paymentService.getResults(paymentRequest);
return new ResponseEntity<PaymentResult>(paymentResult, HttpStatus.OK);
}
}
And here's my test code:
public class RequestPaymentControllerTest {
PaymentService mockPaymentService;
RequestPaymentController requestPaymentController;
HttpServletRequest mockHttpServletRequest;
HttpServletResponse mockHttpServletResponse;
PaymentRequest mockPaymentRequest;
BindingResult mockBindingResult;
LoginService mockLoginService;
PaymentResult mockPaymentResult;
MockMvc mockMvc;
#Before
public void setUp() throws Exception {
mockPaymentService = createMock(PaymentService.class);
mockHttpServletRequest = createMock(HttpServletRequest.class);
mockHttpServletResponse = createMock(HttpServletResponse.class);
mockPaymentRequest = createMock(PaymentRequest.class);
requestPaymentController = new RequestPaymentController();
mockBindingResult = createMock(BindingResult.class);
mockLoginService = createMock(LoginService.class);
requestPaymentController.setPaymentService(mockPaymentService);
mockPaymentResult = createMock(PaymentResult.class);
mockMvc = MockMvcBuilders.standaloneSetup(new RequestPaymentController()).build();
}
#After
public void tearDown() throws Exception {
mockPaymentService = null;
mockHttpServletRequest = null;
mockHttpServletResponse = null;
mockPaymentRequest = null;
requestPaymentController = null;
mockBindingResult = null;
mockLoginService = null;
mockPaymentResult = null;
mockMvc = null;
}
#Test
public void testHandleRequestPayment() throws Exception{
initializeStateForHandleRequestPayment();
createExpectationsForHandleRequestPayment();
replayAndVerifyExpectationsForHandleRequestPayment();
}
private void initializeStateForHandleRequestPayment(){
}
private void createExpectationsForHandleRequestPayment(){
mockPaymentRequest.getServiceUsername();
expectLastCall().andReturn("testuser");
mockPaymentRequest.getServicePassword();
expectLastCall().andReturn("password1!");
mockLoginService.isLoginValid(mockPaymentRequest,mockBindingResult);
expectLastCall().andReturn(true);
mockPaymentService.getResults(mockPaymentRequest);
expectLastCall().andReturn(mockPaymentResult);
}
private void replayAndVerifyExpectationsForHandleRequestPayment() throws Exception{
replay(mockPaymentService, mockBindingResult, mockHttpServletRequest, mockHttpServletResponse, mockPaymentRequest, mockLoginService);
requestPaymentController.setLoginService(mockLoginService);
requestPaymentController.handleRequestPayment(mockPaymentRequest, mockHttpServletRequest, mockHttpServletResponse, mockBindingResult);
mockMvc.perform(post("/requestpayment")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isBadRequest());
verify(mockPaymentService, mockBindingResult, mockHttpServletRequest, mockHttpServletResponse, mockPaymentRequest, mockLoginService);
}
}
The results of the andDo(print()) are:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /requestpayment
Parameters = {}
Headers = {Content-Type=[application/json], Accept=[application/json]}
Handler:
Type = portal.echecks.controller.RequestPaymentController
Method = public org.springframework.http.ResponseEntity<portal.echecks.model.PaymentResult> portal.echecks.controller.RequestPaymentController.handleRequestPayment(portal.echecks.model.PaymentRequest,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,org.springframework.validation.BindingResult) throws java.lang.Exception
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotReadableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 400
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Process finished with exit code 0
As you can see, the test passes when I'm expecting a bad request status, but I've put in logging and I know that the ResponseBody I'm sending back has a 200 status. Like I said, this is my first time with MockMvc, so I assume I've not set something up right. Any suggestions?
An HttpMessageNotReadableException is
Thrown by HttpMessageConverter implementations when the read method
fails.
You also get a 400 Bad Request in your response. This should all tell you that you are not sending what your server is expecting. What is your server expecting?
#RequestMapping(value = "/requestpayment", method = RequestMethod.POST, headers="Accept=application/json")
#ResponseBody
public ResponseEntity<PaymentResult> handleRequestPayment(#RequestBody PaymentRequest paymentRequest, HttpServletRequest request, HttpServletResponse response, BindingResult result) throws Exception{
The main thing here is the #RequestBody annotated parameter. So you are telling your server to try and deserialize a PaymentRequest instance from the body of the HTTP POST request.
So let's see the request you are making
mockMvc.perform(post("/requestpayment")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isBadRequest());
I don't see you providing a body to the request. There should be a content(String) call somewhere in there to set the content of the POST request. This content should be a JSON serialization of a PaymentRequest.
Note that because you are using the StandaloneMockMvcBuilder, you might need to set the HttpMessageConverter instances yourself, ie. a MappingJackson2HttpMessageConverter to serialize and deserialize JSON.
Note that the BindingResult parameter should come immediately after the parameter to which it's related. Like so
#RequestMapping(value = "/requestpayment", method = RequestMethod.POST, headers="Accept=application/json")
#ResponseBody
public ResponseEntity<PaymentResult> handleRequestPayment(#Valid #RequestBody PaymentRequest paymentRequest, BindingResult result, HttpServletRequest request, HttpServletResponse response) throws Exception{
Don't forget the #Valid.
Note that this
requestPaymentController.setLoginService(mockLoginService);
requestPaymentController.handleRequestPayment(mockPaymentRequest, mockHttpServletRequest, mockHttpServletResponse, mockBindingResult);
is completely unrelated to the MockMvc test you are doing.
In my case, as sprint mvc w/ jackson (jackson-mapper-asl, v-1.9.10) deserialization requires JSON parser. And jackson requires a default constructor for http request message deserialization, if there's no default constructor, jackson will have a problem w/ reflection and throws HttpMessageNotReadableException exception.
This is to say, all the classes/sub-classes which used as Request body, (in this case) requires a default constructor. This costed me a few moments after I tried adding custom converter and other suggestions I got in stackoverflow in vain.
Or you can add Custom Deserializer or Mixin annotation to avoid adding default constructor hierachically everywhere. as described here: http://blogs.jbisht.com/blogs/2016/09/12/Deserialize-json-with-Java-parameterized-constructor. Check this if you're interested.
Seems duplicated here > Spring HttpMessageNotReadableException.
Make sure of the following:
return object implements Serializable
#ResponseBody annotation used on the controller method
On your unit test
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {....})
#WebMvcTest
#AutoConfigureMockMvc
Probably too late to answer but just in case someone is still looking at this page.
As #Sotirios Delimanolis mentions, the problem is due to a bad request - a '#RequestBody' is specified in the parameter but never supplied in the request body. So, if you add that to request using 'content(someRequestString)' as below, it should work.
PaymentRequest paymentRequest = new PaymentRequest(...);
String requestBody = new ObjectMapper().valueToTree(paymentRequest).toString();
mockMvc.perform(post("/requestpayment")
.content(requestBody)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("SUCCESS"))
.andExpect(jsonPath("$.paymentAmount", is(20)));
jsonPath may be used to verify the attributes on the response. In the above example, say PaymentResponse has attributes status and paymentAmount in the json response. These parts can be verified easily.
You may run into errors like -
NoClassDefFoundError: com/jayway/jsonpath/Predicate
while using jsonPath. So, make sure it is added to classpath explicitly as it is an optional dependency in spring-test and will not be available transitively. If using maven, do this:
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.4.0</version>
<scope>test</scope>
</dependency>

Resources