JUnit test of post request with multipart/form-data returns 406 - spring-boot

I want to test the following code:
#PostMapping(
path = "/api/v1/news",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> createNews(#RequestParam("file") Optional<MultipartFile> file, #RequestParam("newsData") String newsEntryString) throws IOException {
ObjectMapper mapper = new ObjectMapper()
.findAndRegisterModules()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
NewsEntryDto newsEntryDto = mapper.readValue(newsEntryString, NewsEntryDto.class);
return new ResponseEntity<>(newsService.insertNewNewsEntry(newsEntryDto, file), HttpStatus.OK);
}
So I didn't know how to create a multipart/form-data object inside of my test. I only found the option only to send a file (file), but not a file and further content(newsData).
Currently my test looks like this:
public void shouldInsertNewsEntry() throws Exception {
NewsEntryDto newNewsEntry = new NewsEntryDto();
newNewsEntry.setNewsDate(NEWS_DATE_3);
newNewsEntry.setNewsText(NEWS_TEXT_3);
newNewsEntry.setNewsTitle(NEWS_TITLE_3);
newNewsEntry.setNewsAuthor(NEWS_AUTHOR_3);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
String json = mapper.writeValueAsString(newNewsEntry);
File file3 = new File("src/test/resources/upload/test_3.jpg");
FileInputStream input3 = new FileInputStream(file3);
MockMultipartFile multipartFile3 = new MockMultipartFile("file",
file3.getName(), "image/jpg", IOUtils.toByteArray(input3));
final var mvcResult = this.mockMvc.perform(
MockMvcRequestBuilders.multipart("/api/v1/news/")
.file(multipartFile3)
.content(json)
.accept(MediaType.MULTIPART_FORM_DATA_VALUE))
.andExpect(status().isOk())
.andReturn();
var response = mvcResult.getResponse().getContentAsString();
assertNotNull(response);
}
But in this case I receive the Status 406.
I really don't know if I am on the right way or if there is a simpler solution.
I hope my problem is understandable.

Related

what code should i modify in this SpringBoot org.opentest4j.AssertionFailedError?

made a test code but failed,
the Error is :
error
and here's the test code
#Test
public void Posts_update() throws Exception {
Posts savedPosts = postsRepository.save(Posts.builder()
.title("title")
.content("content")
.author("author")
.build());
Long updateId = savedPosts.getId();
String expectedTitle = "title2";
String expectedContent = "content2";
PostsUpdateRequestDto requestDto = PostsUpdateRequestDto.builder()
.title(expectedTitle)
.content(expectedContent)
.build();
String url = "http://localhost:" + port + "/api/v1/posts/" + updateId;
HttpEntity<PostsUpdateRequestDto> requestEntity = new HttpEntity<>(requestDto);
// when
ResponseEntity<Long> responseEntity = restTemplate.exchange(url, HttpMethod.PUT, requestEntity, Long.class);
// then
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(responseEntity.getBody()).isGreaterThan(0L);
List<Posts> all = postsRepository.findAll();
assertThat(all.get(0).getTitle()).isEqualTo(expectedTitle);
assertThat(all.get(0).getContent()).isEqualTo(expectedContent);
}
I'm trying this from a book but i can't find answer here.
You are saving entity with title "title" and expecting it to be "title2" when you fetch it. Either change your entity data or expected title. Replace repository save with below
Posts savedPosts = postsRepository.save(Posts.builder()
.title("title2")
.content("content")
.author("author")
.build());
This should work

Create mock server to test on result of RestTemplate

I am not sure if it is possible to write a Test case that can mock the "http://localhost:8888/setup" site, so the above code can hit it and I want to check if the "http://localhost:8888/setup" received the inputStream correctly.
InputStream inputStream = //got the inputStream;
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);
InputStreamResource inputStreamResource = new InputStreamResource(inputStream){
#Override
public String getFilename(){
return filename;
}
#Override
public long contentLength(){
return -1;
}
}
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>():
body.add("file", inputStreamResource);
HttpHeader headers = new HttpHeader();
headers.setContentType(MediaType.MULTIPART_FORM_DATA)LinkedMultiValueMap
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
String url = "http://localhost:8888/setup";
restTemplate.postForObject(url, requestEntity, String.class);
Try using Wiremock!
Many ways of using it, back then when I used it, I used to run a JAR (wiremock jar) and it spawns up a program on your localhost with your port specified. Henceforth, you can test by hitting that localhost on the port it's up!
For reference check this out :
https://www.softwaretestinghelp.com/wiremock-tutorial/
https://www.baeldung.com/introduction-to-wiremock
https://github.com/wiremock/wiremock

Okhttp create MultipartBody with spring MultipartFile and json object

I have a controller like so that accepts a MultipartFile and json object:
#PostMapping(value = "/v1/submit")
public ResponseEntity submit(
#RequestParam(value="myFile", required = true) MultipartFile myFile
, #Valid #RequestPart(value="fileMeta", required=true) FileMeta fileMeta
){
I need to forward this to a new url using an okhttpclient post with a Multipartbody containing both myFile and fileMeta objects:
OkHttpClient client = new OkHttpClient();
MultipartBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("myFile", myFile.getName(), okhttp3.RequestBody.create(file, MediaType.parse("pdf"))
.addFormDataPart("fileMeta", fileMeta)
.build();
I am getting following error:
Cannot resolve method 'create(org.springframework.web.multipart.MultipartFile, okhttp3.MediaType)'
The method definition of OkHttp's RequestBody create is the following: create(MediaType contentType, byte[] content). It expects the first the MediaType and second the payload (either as byte[], File or other formats).
So you first have to switch the order of the method arguments and second convert the MultipartFile from Spring to a proper format that the create() method accepts, e.g. byte[] or File:
OkHttpClient client = new OkHttpClient();
MultipartBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("myFile", myFile.getName(), RequestBody.create(MediaType.parse("pdf"), file)
.addFormDataPart("fileMeta", fileMeta)
.build();
There are already multiple solutions available on StackOverflow to convert MultipartFile to File: How to convert a multipart file to File?
UPDATE: Example for using RestTemplate
#RestController
public class FileSendingController {
#PostMapping("/files")
public void streamFile(#RequestParam("file") MultipartFile file) {
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", file);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
RestTemplate restTemplate = new RestTemplate();
restTemplate.postForEntity("http://upload.to", requestEntity, String.class);
}
}

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.

Spring RestTemplate POST upload multiple files

Let's assume I have an endpoint looking like the one below:
#PostMapping(
value = "/something",
consumes = MULTIPART_FORM_DATA_VALUE,
produces = APPLICATION_JSON_VALUE)
public SomeDTO post2Files(
#RequestPart("file1") MultipartFile file1,
#RequestPart("file2") MultipartFile file2 {
In another service I want to read one file from the file system and just resend it, while the file2 is actually a string that I wanna pass as a file through RestTemplate.
I tried something like this:
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file1", new FileSystemResource(somePath));
body.add("file2", new ByteArrayResource(someString.getBytes()));
restTemplate.postForObject("/something", new HttpEntity<>(body, headers), SomeDTO.class)
It doesn't work and I have no clue why. I get 400. What should I do to make the request pass through?
Figured it out.
This is the solution:
body.add("dataSchema", new ByteArrayResource(someString.getBytes()) {
#Override
public String getFilename() {
return "file2";
}
});
It didn't work because the filename did not match with the #RequestPart.

Resources