I'm trying to test my rest api with mockMvc.
mockMvc.perform(get("/users/1/mobile")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andExpect(content().string("iPhone"))
The test failed because of:
java.lang.AssertionError: Response content
Expected :iPhone
Actual :
From the output of print(), I can know the API actually returned the expected string "iPhone".
ModelAndView:
View name = users/1/mobile
View = null
Attribute = treeNode
value = "iPhone"
errors = []
And I guess the empty "Actual" above is caused by empty "Body" below
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = users/1/mobile
Redirected URL = null
Cookies = []
My questions are:
Why MockHttpServletResponse's Body is empty;
How can I correctly test the response of API.
If your action methods (methods with #RequestMapping annotation) return instances of ModelAndView or you work with Model, you have to test it using MockMvcResultMatchers#model function:
.andExpect(MockMvcResultMatchers.model().attribute("phone", "iPhone"))
.andExpect(MockMvcResultMatchers.model().size(1))
MockMvcResultMatchers#content is appropriate for REST action methods (methods with #RequestBody annotation).
To have a better understanding about testing Spring MVC and Spring REST controllers check these links:
Testing of Spring MVC Applications: Forms
Testing of Spring MVC Applications: REST API
Just adding another reason for this error, that took me a whole day to discover. I successfully created an APITest using mockito and mockmvc class, using the perform method. Then copied the code to produce another service and I started to get an empty body over and over again.
Nonetheless, at the end of the day I decided to compare each copied class from one project to another. The only one difference that I found was the #EqualsAndHashCode annotation in my request DTO that is received by the new controller.
So, the recommendation is: add the #EqualsAndHashCode annotation in your DTO classes.
Related
I have written API's which are resulting in 500 error when hit through postman or browser. However, when I debug and see server is not throwing any error and in fact returning a proper response. Other controller I have implemented in a similar way is returning expected result. below is my controller code. Has anyone faced similar situation. Kindly help.
#CrossOrigin
#GetMapping(value="/byPatientId/{patientId}", produces = "application/json")
public List<ContactInfo> getAllContacts(#PathVariable String patientId) {
logger.info("Received request for List of ContactInfo for patientId: "+patientId);
List<ContactInfo> list =
contactInfoService.getAllContacts(patientId);
return list;
}
#CrossOrigin
#GetMapping("/byContactId/{contactId}")
public ContactInfo getContactById(#PathVariable Integer contactId) {
logger.info("Received request for ContactInfo for contactId: "+contactId);
return contactInfoService.getContactById(contactId);
}
The problem was with one of the dependent object which was having oneToMany relationship with the return type object and it was set to Lazy loading and issue was during the serialization.
Either we can change it to Eager loading or ignore the dependent object by adding #JsonIgnore on dependent object.
I handled it by adding #JsonIgnore annotation on top of the dependent object as I don't need the dependent object in this particular usecase. Issue is solved now.
How is your Controller annotated? is it with #Controller or #Rest?
#RestController = #Controller + #ResponseBody(for serializing the response and pass it into the HttpResponse.
Add the #ResponseBody in your methods on the controller or change the #Controller tag into a #RestController(take into account that #RestController is available since 4.0 Spring version).
More info:https://www.baeldung.com/spring-controller-vs-restcontroller
I wrote the piece of code below:
#PostMapping(path = "/process", produces = MediaType.APPLICATION_JSON_VALUE)
#ApiOperation(value = "Get process with given ID", produces = MediaType.APPLICATION_JSON_VALUE,
response = ProcessType.class)
public ResponseEntity<ProcessType> createProcessType(
#RequestBody
#DTO(ProcessTypeDto.class) ProcessType processType
) {
log.info("POST called on /process");
ProcessType processTypeResult;
...
...
}
which works great. But my problem is with swagger. I made a custom annotation #DTO which automatically maps one class to another. But, swagger sees my ProcessType request body and shows examples in the UI of that class rather than ProcessTypeDto. If I delete what swagger shows and POST ProcessTypeDto the code works I would just like swagger to show ProcessTypeDto as the default example for this endpoint as it would break codegen.
Is there a way to manually specify what request body I would like from swaggers POV overriding what my#Requestbody is?
looks like you are not lucky so far, it will be released in version 2.0
here is what you are lookin for https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations#requestbody
or at least you can start using a release candidate
https://mvnrepository.com/artifact/io.swagger/swagger-core
In my Spring boot app replacing a legacy, i have defined a webservice EndPoint.
soem of the user today comes in with payload that does nothave the namespace URI.
since namespace is not there, Spring throws No Endpoint mapping found error.
Is there a way i can add a default Endpoint so that it will get invoked if no mapping is found.
Thanks
You can try the following to create a fallback method for all request
#RequestMapping(value = "*", method = RequestMethod.GET)
#ResponseBody
public String getFallback() {
return "Fallback for GET Requests";
}
You can get more information here https://www.baeldung.com/spring-requestmapping
I have a spring boot controller endpoint as follows.
#PutMapping("/manage/{id}")
public ResponseEntity<Boolean> manage(#PathVariable Long id, #RequestBody Type type) {
...
}
Where Type is an Enum as follows.
public enum Type {
ONE,
TWO
}
ISSUE 1: When I test this controller, I have to send the content as "ONE" instead of ONE for a successful invocation. i.e. it works with the following code.
mvc.perform(put("/api/manage/1")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content("\"" + Type.ONE + '\"'))
.andExpect(status().isOk());
It does not work with
mvc.perform(put("/api/manage/1")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(Type.ONE.name()))
.andExpect(status().isOk());
ISSUE 2: I am not able to invoke this method from the Angular service.
this.http.put<string>('/api/manage/' + id, type)
gives me
org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'text/plain;charset=UTF-8' not supported
Everything works when I add the Enum to a Dto and send an object from the client. But due to some business requirements, I want to use the current structure itself. i.e the Enum as a RequestBody.
UPDATE
I also tried to change the controller method structure to
#PutMapping(value = "/manage/{id}", consumes = MediaType.TEXT_PLAIN_VALUE)
I get the following error.
Content type 'text/plain' not supported
Both issues stem from trying to use a JSON endpoint as a plain text endpoint.
Ad 1, ONE is invalid JSON ("ONE" is valid)
Ad 2, when you just post a string, it is sent as text/plain and the endpoint complains.
Probably adding consumes="text/plain" to your #PutMapping will solve the problem, but frankly - I am not sure if string/enum mappings work out-of-the-box in the hodge-podge that is spring boot.
I am working on a Spring REST application.
This application has only REST controllers, no view part.
I want to know how can I validate a #RequestParam
For example
#RequestMapping(value = "", params = "from", method = RequestMethod.GET)
public List<MealReadingDTO> getAllMealReadingsAfter(#RequestParam(name = "from", required = true) Date fromDate) {
......
......
}
In the above example, my goal is to validate the Date. Suppose someone pass an invalid value, then I should be able to handle that situation.
Now it is giving and exception with 500 status.
PS
My question is not just about Date validation.
Suppose, there is a boolean parameter and someone passes tru instead of true by mistake, I should be able to handle this situation as well.
Thanks in advance :)
Spring will fail with an 500 status code, because it cannot parse the value.
The stages of request handling are:
receive request
identify endpoint
parse request params / body values and bind them to the detected objects
validate values if #Validated is used
enter method call with proper parameters
In your case the flow fails at the parse (3) phase.
Most probably you receive a BindException.
You may handle these cases by providing an exception handler for your controller.
#ControllerAdvice
public class ControllerExceptionHandler {
#ExceptionHandler(BindException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public YourErrorObject handleBindException(BindException e) {
// the details which field binding went wrong are in the
// exception object.
return yourCustomErrorData;
}
}
Otherwise when parsing is not functioning as expected (especially a hussle with Dates), you may want to add your custom mappers / serializers.
Most probably you have to configure Jackson, as that package is responsible for serializing / deserializing values.