I've a Spring (3.0) Controller with a method which has HttpServletRequest as one of the parameters, since it's handling (multiple) file uploads.
#RequestMapping(value = "/classified/{idClassified}/dealer/{idPerson}/upload",
method = RequestMethod.POST)
#ResponseBody
public final String uploadClassifiedPicture(
#PathVariable int idClassified,
#PathVariable int idPerson,
#RequestParam String token,
HttpServletRequest request);
How to Unit Test it? I know I can create a MockHttpServletRequest, but I don't know how to pass one or more files to it.
MockHttpServletRequest request = new MockHttpServletRequest("POST",
"/classified/38001/dealer/54/upload?token=dfak241adf");
I recommend to change the method signature a bit, to make the uploaded file a normal parameter (of type MultipartFile (not CommonsMultipartFile)):
#RequestMapping(value = "/classified/{idClassified}/dealer/{idPerson}/upload",
method = RequestMethod.POST)
#ResponseBody
public final String uploadClassifiedPicture(
#PathVariable int idClassified,
#PathVariable int idPerson,
#RequestParam String token,
#RequestParam MultipartFile content);
Then you can use a MockMultipartFile in your test:
final String fileName = "test.txt";
final byte[] content = "Hallo Word".getBytes();
MockMultipartFile mockMultipartFile =
new MockMultipartFile("content", fileName, "text/plain", content);
uploadClassifiedPicture(1, 1, "token", mockMultipartFile);
If you do not want to change the method signature, then you can use MockMultipartHttpServletRequest instead.
It has a method addFile(MultipartFile file). And of course the required parameter can be a MockMultipartFile.
You can also use the MockMvc object as well as MockMvcRequestBuilders to send a test file upload request to your controller:
#Test
public void testSendNotEmptyFile() throws Exception {
mvc.perform(MockMvcRequestBuilders.fileUpload("Your controller URL")
.file("file", "Test Content".getBytes())
.contentType(MediaType.MULTIPART_FORM_DATA)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
Related
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")));
}
I'm trying to create a Unit test in a Spring REST application. the test is related to an endpoint for MultipartFile uploading.
Here is the arguments that my method accepts within my #RestController
#PostMapping("/upload")
public ResponseEntity uploadFile(#HasFileName #RequestParam("file") MultipartFile file,
#NotEmpty #RequestParam("entity") String entity, #NotEmpty #RequestParam("language") String language,
#NotEmpty #RequestParam("lastModified") #DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime lastModifiedDateTime,
#NotEmpty #RequestParam("createdDate") #DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime createdDateTime)
throws IOException {
I can't find a way to get this information from the multipart inside the unit test. I can't find documentation on making spring unit test for Spring REST, so any inputs would be appreciated.
#Test
public void testUploadFile() throws Exception{
ResultMatcher ok = MockMvcResultMatchers.status().isOk();
String filename = "test.txt";
File file = new File("/test" + filename);
file.delete();
MockMultipartFile mockMultipartFile = new MockMultipartFile("file", "test.txt", "multipart/form-data", "test data".getBytes());
upload.uploadFile(mockMultipartFile, mockMultipartFile.getName()) //missing more data
//more logic on how to assert that the file is not empty
}
In principle the test should validate the outcome of the uploadFile method():
ResultActions result =
this.mockMvc.perform(multipart("/svc").file(file)
.header(HttpHeaders.AUTHORIZATION,
"Bearer token"))
.andExpect(content().string("response"))
.andExpect(status().isOk());
The file is an input parameter so I dont understand why you want to verify it is not empty: it could be useful I believe to verify the interactions with other mocks (for instance checking the file is used in a FileService.save() method)
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
I am trying to get a JSON Object from an API while using an API key in the header.
This works perfectly when I test it in Postman, but when I try it in my Spring application.
I got an error:
There was an unexpected error (type=Not Found, status=404). No message available.
API-Key and the URL are changed out with dummy data
#RequestMapping(value = "/apitest", method = RequestMethod.GET, headers ="APIKey=12345")
public #ResponseBody void testingAPI() throws ParseException {
final RestTemplate restTemplate = new RestTemplate();
final String response = restTemplate.getForObject("url", String.class);
System.out.println(response);
}
If your are testing your API in Postman and it works perfectly, and in your application it's not working, this means that your method mapping isn't correct or it's not correctly called.
But from the comments where you said that the same configuration works if you don't have an API key, this means that your header isn't correctly mapped, in this case I'd recommend using #RequestHeader annotation to handle your API key.
Your method mapping will be like this:
#RequestMapping(value = "/apitest", method = RequestMethod.GET)
public #ResponseBody void testingAPI(#RequestHeader("APIKey") String apiKey) throws ParseException {
final RestTemplate restTemplate = new RestTemplate();
final String response = restTemplate.getForObject("url", String.class);
System.out.println(response);
}
If you want to use 12345 as a default value for your API key param you can write:
#RequestMapping(value = "/apitest", method = RequestMethod.GET)
public #ResponseBody void testingAPI(#RequestHeader(name = "APIKey", defaultValue = "12345") String apiKey) throws ParseException {
You can check How to Read HTTP Headers in Spring REST Controllers tutorial for further reading about the #RequestHeader annotation.
A quick fix could be to change the void to a Class. like
#RequestMapping(value = "/apitest", method = RequestMethod.GET, headers ="APIKey=12345")
#ResponseBody
public XXXResponse testingAPI() throws ParseException {
...
return new XXXRepsonse();
}
or:
#RequestMapping(value = "/apitest", method = RequestMethod.GET, headers ="APIKey=12345")
public void testingAPI() throws ParseException {
...
}
Where are you add header in your request? You controller should look like this:
#RestController
public class DemoController {
#GetMapping("/apitest" )
public void doRequest(#RequestHeader(name = "Ocp-Apim-Subscription-Key", defaultValue = "12345") String apiKey) {
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("Ocp-Apim-Subscription-Key", apiKey);
ResponseEntity<String> responseEntity = restTemplate.exchange("https://api.kognif.ai/AIS/v1/aispositioncurrent?vesselimo=8505941&output=json",
HttpMethod.GET, new HttpEntity<String>(headers), String.class);
System.out.println(responseEntity.toString());
}
}
Postman request to your Spring app must be :
And of course, specify valid Ocp-Apim-Subscription-Key
I am trying to upload a file in chunks in Java.
My chunk upload function:
#Async
private Future<String> sendChunk(byte[] chunk, int start, int end, int numberOfChunks, String name, String sessionId) throws IOException {
LinkedMultiValueMap<String, Object> requestParams = new LinkedMultiValueMap<>();
requestParams.add("data", new String(java.util.Base64.getEncoder().encode(chunk), "UTF-8"));
requestParams.add("start", start);
requestParams.add("end", end);
requestParams.add("numberOfChunks", numberOfChunks);
requestParams.add("fileName", name);
requestParams.add("session", sessionId);
HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<>(requestParams);
UploadResponse uploadResponse = restTemplate.exchange(fileServiceUrl, HttpMethod.POST, requestEntity, new ParameterizedTypeReference<UploadResponse>(){}).getBody();
return new AsyncResult<>(uploadResponse.getSessionId());
}
This is how the File-Service-API looks like:
#RequestMapping(value = "/simpleUploader", method = RequestMethod.POST, produces = "application/json")
public ResponseEntity<UploadResponse> simpleUploader(
#RequestParam("data") String data,
#RequestParam("session") String sessionId,
#RequestParam("start") int start,
#RequestParam("end") int end,
#RequestParam("numberOfChunks") int numberOfChunks,
#RequestParam("fileName") String fileName) throws IOException {
....
}
Now, if I try to upload a Chunk the File-Service responds with a 400 - Bad Request.
Stack-Trace:
org.springframework.web.client.HttpClientErrorException: 400 null
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:667)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:620)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:580)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:526)
It will be rejected from Spring even before it gets to the API-Function.
I am already uploading the same way, but in Javascript. From Javascript everything is working as it should.
What am I missing?
The only problem I had with my code was, that "sessionId" will be null the first time I am calling the File-Service API.
When I set a value of a Map Entry to null, the whole Entry doesn't get passed. I didnt knew that.
My fix was to add a defaultValue to #RequestParam("session") String sessionId so that the method declaration looks like that:
#RequestMapping(value = "/simpleUploader", method = RequestMethod.POST, produces = "application/json")
public ResponseEntity<UploadResponse> simpleUploader(
...
#RequestParam(value = "session", defaultValue = "") String sessionId,
... ) throws IOException {
....
}
The #Async has two limitations.
it must be applied to public methods only.
self-invocation – calling the async method from within the same class – won’t work
http://www.baeldung.com/spring-async
Try changing private to public
#Async
public Future<String> sendChunk(byte[] chunk, int start, int end, int numberOfChunks, String name, String sessionId) throws IOException {