Using Spring boot 2 and Spring mvc. I am trying to test my rest controller using mockMvc
#PostMapping(
value = "/attachment")
public ResponseEntity attachment(MultipartHttpServletRequest file, #RequestBody DocumentRequest body) {
Document document;
try {
document = documentService.process(file.getFile("file"), body);
} catch (IOException | NullPointerException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
return ResponseEntity.accepted().body(DocumentUploadSuccess.of(
document.getId(),
"Document Uploaded",
LocalDateTime.now()
));
}
I could attach the file successfully on my test but know I added a body and I can't receive both attached
#Test
#DisplayName("Upload Document")
public void testController() throws Exception {
byte[] attachedfile = IOUtils.resourceToByteArray("/request/document-text.txt");
MockMultipartFile mockMultipartFile = new MockMultipartFile("file", "",
"text/plain", attachedfile);
DocumentRequest documentRequest = new DocumentRequest();
documentRequest.setApplicationId("_APP_ID");
MockHttpServletRequestBuilder builder =
MockMvcRequestBuilders
.fileUpload("/attachment")
.file(mockMultipartFile)
.content(objectMapper.writeValueAsString(documentRequest));
MvcResult result = mockMvc.perform(builder).andExpect(MockMvcResultMatchers.status().isAccepted())
.andDo(MockMvcResultHandlers.print()).andReturn();
JsonNode response = objectMapper.readTree(result.getResponse().getContentAsString());
String id = response.get("id").asText();
Assert.assertTrue(documentRepository.findById(id).isPresent());
}
I got 415 status error
java.lang.AssertionError: Status expected:<202> but was:<415>
Expected :202
Actual :415
How could I fix it?
You're getting status 415: unsupported media type.
You needed to changed add contentType() of the request which the controller accepts.
If your controller accepts application/json:
MockHttpServletRequestBuilder builder =
MockMvcRequestBuilders
.multipart("/attachment")
.file(mockMultipartFile)
.content(objectMapper.writeValueAsString(documentRequest))
.contentType(MediaType.APPLICATION_JSON);// <<<
Related
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.
PTAControllerTest.Java
#Test
public void testCreateProducerTeamAccess() throws Exception {
String expectedProducerId = "AGT02";
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
// Instantiate request DTO
ProducerTeamAccessPk newProducerTeamAccessPk = new ProducerTeamAccessPk(expectedProducerId, "ATL");
ProducerTeamAccessDto requestProducerTeamAccessDto = createMockProducerTeamAccessDto(newProducerTeamAccessPk);
ProducerTeamAccess createdProducerTeamAccess = producerTeamAccessMapper.map(requestProducerTeamAccessDto);
Mockito
.when(producerService.exists(Mockito.anyString()))
.thenReturn(true);
Mockito
.when(producerTeamAccessRepository.findById(ArgumentMatchers.any(ProducerTeamAccessPk.class)))
.thenReturn(Optional.of(createdProducerTeamAccess));
Mockito
.when(producerTeamAccessRepository.saveAndFlush(ArgumentMatchers.any(ProducerTeamAccess.class)))
.thenReturn(createdProducerTeamAccess);
Mockito
.doNothing()
.when(messageSender)
.sendCreated(Mockito.any(BaseDto.class));
String requestProducerTeamAccessDtoJson = objectMapper.writeValueAsString(requestProducerTeamAccessDto);
// remove keys that are not allowed to be set
DocumentContext doc = JsonPath.parse(requestProducerTeamAccessDtoJson);
doc.delete("links");
requestProducerTeamAccessDtoJson = doc.jsonString();
RequestBuilder request = RestDocumentationRequestBuilders
.post(controllerPath, expectedProducerId)
.content(requestProducerTeamAccessDtoJson)
.contentType(CustomMediaType.APPLICATION_AIM_API_V1_JSON_UTF8)
.accept(CustomMediaType.APPLICATION_AIM_API_V1_JSON_UTF8);
MvcResult result = this.mockMvc
.perform(request)
.andExpect(status().isCreated())
.andDo(document("{method-name}",
resourceDetails()
.description("To create a new ProducerTeamAccess resource, POST a JSON resource representation to the /producerTeamAccesss endpoint with the content being a single JSON object containing all of the required ProducerTeamAccess fields, and additionally any optional fields. You may include the TeamId field in your request, but it will be ignored, and instead will be saved with the newly created resource ID as assigned by the server. " +
"Upon acceptance and successful processing a request to create a new ProducerTeamAccess resource, the server will respond with a 201 Created and a Location header indicating the URI of the newly created ProducerTeamAccess resource.")
.summary("Create a ProducerTeamAccess resource")
.requestSchema(Schema.schema("ProducerTeamAccessDto"))
.tag(TAG),
pathParameters(
parameterWithName("producerId").description("producerId of the ProducerTeamAccess to create")),
getRequestProducerTeamAccessDto(false)))
.andReturn();
}
PTAService.java
public ProducerTeamAccessDto save(String producerId, ProducerTeamAccessCreateDto producerTeamAccessCreateDto) {
this.validate(producerId, producerTeamAccessCreateDto);
ProducerTeamAccess ProducerTeamAccessToSave = producerTeamAccessMapper.mapFromCreateDto(producerTeamAccessCreateDto);
ProducerTeamAccessToSave.setProducerId(producerId);
ProducerTeamAccessToSave.setCreatedById(UserSession.getUserId());
ProducerTeamAccessToSave.setDateAdded(Date.now());
ProducerTeamAccessToSave.setModifiedById(UserSession.getUserId());
ProducerTeamAccessToSave.setDateModified(Date.now());
ProducerTeamAccess newProducerTeamAccess = ProducerTeamAccessRepository.saveAndFlush(ProducerTeamAccessToSave);
return producerTeamAccessMapper.map(newProducerTeamAccess);
}
public ResponseEntity<Void> createProducerTeamAccess(
#PathVariable("producerId") String producerId,
#RequestBody ProducerTeamAccessCreateDto producerTeamAccessCreateDto, UriComponentsBuilder ucBuilder) {
logger.info("Creating ProducerTeamAccess.");
ProducerTeamAccessDto newProducerTeamAccessDto = producerTeamAccessService.save(producerId, producerTeamAccessCreateDto);
try {
messageSender.sendCreated(newProducerTeamAccessDto);
}
catch (Exception e) {
logger.error("Exception occured while sending the ProducerTeamAccess create message to the Queue");
}
// redirect to newly created ProducerTeamAccess record
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ucBuilder.path(controllerPath + "/{teamId}").buildAndExpand(newProducerTeamAccessDto.getTeamId()).toUri());
return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}
i have a problem testing an endpoint which use #ModelAttribute I don't know very well how to test with this annotation and the test response is java.lang.AssertionError: Content type not set , here is the controller method:
#PostMapping
public ResponseEntity<?> createTestimonials(#ModelAttribute(name = "testimonialsCreationDto") #Valid TestimonialsCreationDto testimonialsCreationDto) {
try {
return ResponseEntity.status(HttpStatus.CREATED).body(iTestimonials.createTestimonials(testimonialsCreationDto));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(e.getMessage());
}
}
Here is the test:
#Test
void createTestimonials() throws Exception {
//Given
String name = "Testimonio 159";
String contentTestimonial = name + " content!";
TestimonialsCreationDto testimonialsCreationDto = new TestimonialsCreationDto();
testimonialsCreationDto.setName(name);
testimonialsCreationDto.setContent(contentTestimonial);
//When
mockMvc.perform(post("/testimonials")
.flashAttr("testimonialsCreationDto", testimonialsCreationDto)
.contentType(MediaType.MULTIPART_FORM_DATA)
.content(objectMapper.writeValueAsString(testimonialsCreationDto))
.characterEncoding("UTF-8"))
//Then
.andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.name", is(name)))
.andExpect(jsonPath("$.content", is(contentTestimonial)));
verify(testimonialsService).createTestimonials(any());
}
MockHttpServletRequest:
HTTP Method = POST
Request URI = /testimonials
Parameters = {}
Headers = [Content-Type:"multipart/form-data;charset=UTF-8", Content-Length:"74"]
Body = {"name":"Testimonio 159","image":null,"content":"Testimonio 159 content!"}
Session Attrs = {}
MockHttpServletResponse:
Status = 200 ---> IDK why response with 200 code
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Content type not set
You have to add path value to PostMapping :
#PostMapping(path = "/testimonials", produces = MediaType.MULTIPART_FORM_DATA)
I'm trying to attach a file to send to and endpoint as a MultipartFile but I'm getting this exception:
Expected no exception to be thrown, but got 'feign.codec.EncodeException'
//...
Caused by: feign.codec.EncodeException: Could not write request:
no suitable HttpMessageConverter found for request type [java.util.LinkedHashMap]
and content type [multipart/form-data]
My method is:
//...
final User user
//...
#Override
DocumentResponse attachDocument(File file, String userId, String documentId) {
String timestamp = String.valueOf(System.currentTimeMillis())
String url = "${myProperties.apiUrl}/documents/attach?ts=${timestamp}"
String digest = myJWT.sign(HttpMethod.POST, url)
MultipartFile multiFile = new MockMultipartFile("test.xml",
new FileInputStream(file))
DocumentResponse documentResponse = user.attachDocument(multiFile,
userId, documentId, timestamp, digest)
return documentResponse
}
My interface is:
#FeignClient(name = 'myUser', url = '${apiUrl}', configuration = myConfiguration)
interface User {
//...
#PostMapping(value = '/documents/attach', consumes = 'multipart/form-data')
DocumentResponse attachDocument(#PathVariable('file') MultipartFile multiFile,
#PathVariable('clientId') String userId,
#PathVariable('documentId') String documentId,
#RequestParam('ts') String timestamp,
#RequestParam('digest') String digest)
}
And my configuration file is:
#Slf4j
#Configuration
class myConfiguration {
#Bean
Retryer feignRetryer(#Value('${feign.client.config.myUser.period}') Long period,
#Value('${feign.client.config.myUser.maxInterval}') Long maxInterval,
#Value('${feign.client.config.myUser.maxAttempts}') Integer maxAttempts) {
return new Retryer.Default(period, maxInterval, maxAttempts)
}
#Bean
ErrorDecoder errorDecoder() {
return new ErrorDecoder() {
#Override
Exception decode(String methodKey, Response response) {
if (HttpStatus.OK.value() != response.status()) {
FeignException ex = FeignException.errorStatus(methodKey, response)
if (response.status() != HttpStatus.BAD_REQUEST.value()) {
return new RetryableException('getting conflict and retry', new Date(System.currentTimeMillis() + TimeUnit.SECONDS
.toMillis(1)))
}
return new MyDocumentException()
}
}
}
}
}
Also, I have tried to add this code to myConfiguration file:
#Bean
Encoder encoder() {
return new FormEncoder()
}
But I have another exception:
Cannot cast object 'feign.form.FormEncoder#5fa78e0a'
with class 'feign.form.FormEncoder' to class 'java.beans.Encoder'
I'm using Spring boot '2.0.2.RELEASE' with:
"io.github.openfeign.form:feign-form:3.4.1",
"io.github.openfeign.form:feign-form-spring:3.4.1",
I checked these posts:
How to send POST request by Spring cloud Feign
no suitable HttpMessageConverter found for response type
Could not write request: no suitable HttpMessageConverter found for request type and content type
Converting file to multipartfile
Any suggestion?
feign.codec.EncodeException raised when a problem occurs encoding a message.
I think the #PathVariable('file') MultipartFile multiFile, can be converted to a base64 sting and pass it to REST API or add an Encoder to MultipartFile
I've an heartbeat API implemeted using Spring REST service:
#RequestMapping(value = "heartbeat", method = RequestMethod.GET, consumes="application/json")
public ResponseEntity<String> getHeartBeat() throws Exception {
String curr_time = myService.getCurrentTime();
return Util.getResponse(curr_time, HttpStatus.OK);
}
And MyService.java has below method:
public String getCurrentTime() throws Exception {
String currentDateTime = null;
MyJson json = new MyJson();
ObjectMapper mapper = new ObjectMapper().configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
try {
Date currDate = new Date(System.currentTimeMillis());
currentDateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").format(currDate);
json.setTime(currentDateTime);
ObjectWriter writer = mapper.writerWithView(Views.HeartBeatApi.class);
return writer.writeValueAsString(json);
} catch (Exception e) {
throw new Exception("Excpetion", HttpStatus.BAD_REQUEST);
}
}
It works as expected but have 2 issues:
When I invoke this API, Content-Type header is mandatory & I want to know how to make this header optional.
How to add "Accept" header so that it can support other format such as Google Protobuf?
Thanks!
If you don't want to require Content-Type exist and be "application/json", you can just omit the consumes section entirely.
"Accept" is available via the "produces" value, as opposed to "consumes." So if you wanted to support Google Protobuf OR application/json, you could do this:
#Controller
#RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json")
#ResponseBody
public ResponseEntity<String> getHeartBeat() throws Exception {
String curr_time = myService.getCurrentTime();
return Util.getResponse(curr_time, HttpStatus.OK);
}