MockMVC JsonPath response has empty body? - java

Testing the controller gives following error: java.lang.AssertionError: No value at JSON path "$.firstName". Apparently the body of my response is empty for some reason. Controller is a RestController and and i'm correctly mocking the service. Can someone help me ?
Testclass:
#WebMvcTest(EmployeeController.class)
class EmployeeControllerTest {
Employee employee = new Employee();
#Autowired
private MockMvc mvc;
#MockBean
private EmployeeServiceImpl service;
#BeforeEach
public void initEmployee() {
employee.setFirstName("John");
employee.setLastName("Doe");
employee.setPlace("xxxx");
employee.setEmployeeTitle(EmployeeTitle.SENIOR_JAVA_DEVELOPER);
employee.setEmployeeId(1L);
}
#Test
public void createEmployeeAPI() throws Exception {
when(service.addNewEmployee(employee)).thenReturn(employee);
mvc.perform(MockMvcRequestBuilders
.post("/employees")
.content(asJsonString(employee))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.firstName").value("John"));
}
Request is correct but apparently the body is of the response is empty (see console output below):
MockHttpServletRequest:
HTTP Method = POST
Request URI = /employees
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json", Content-Length:"124"]
Body = {"employeeId":1,"firstName":"John","lastName":"Doe","employeeTitle":"SENIOR_JAVA_DEVELOPER","place":"xxxx","photoURrl":null}
Session Attrs = {}
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Controller code:
#RestController
#RequestMapping("/employees")
public class EmployeeController extends ExceptionHandling {
private final EmployeeService employeeService;
#Autowired
public EmployeeController(EmployeeService employeeService) {
this.employeeService = employeeService;
}
#PostMapping()
public Employee addEmployee(#RequestBody Employee employee) {
return employeeService.addNewEmployee(employee);
}

The problem lies here:
when(service.addNewEmployee(employee)).thenReturn(employee);
Internally Mockito uses an Object class's equals() method to compare object that has been passed to the method as an argument with object configured. If equals() is not overridden then java.lang.Object’s equals() is used which compares only the references, i.e. if both variables point to one and the same object in heap. In the current example, Employe class (probably) has no equals() method implemented. When providing expected a new object is created, references are not one and the same, so Mockito will fail the verification.
Because the employee object is serialized from json, it is not the same object.
You can fix this by using Mockito.any() or Mockito.any(Employee.class).

Related

Spring boot patch request on controller with Map parameter. Test with mockmvc

I'm trying to test a 'patch request' from my CompanyController that has a Map and Id as a parameters. I expected get a http status 200, but I get a http status 400.
Can someone explain to me what I'm doing wrong? thank you
CompanyController (some parts of the code are omitted):
#RestController
public class CompanyController {
#Autowired
private CompanyService companyService;
#PatchMapping("companies/{id}")
public ResponseEntity<CompanyDTO> patchUpdateCompany(#PathVariable Integer id,
#RequestBody Map<String, Object> updates) throws JsonMappingException {
Optional<CompanyDTO> optionalCompanyDTO = this.companyService.patchUpdateCompany(updates, id);
return ResponseEntity.ok(optionalCompanyDTO.get());
}
}
CompanyControllerTest (some parts of the code are omitted)
#WebMvcTest(CompanyController.class)
public class CompanyControllerTest {
#MockBean
private CompanyService companyService;
#Autowired
private MockMvc mockMvc;
private static List<CompanyDTO> companyDTOList;
#BeforeAll
public static void beforeAll(){
companyDTOList = new ArrayList<>();
CompanyDTO companyDTO1 = CompanyDTO.builder().id(1).name("xavi").build();
CompanyDTO companyDTO2 = CompanyDTO.builder().id(2).name("marteta").build();
companyDTOList.add(companyDTO1);
companyDTOList.add(companyDTO2);
}
#Test
void givenMapAndIdWhenPatchUpdateCompanyThenReturnHttpStatusOk() throws Exception {
Mockito.when(this.companyService.getCompanyById(1)).thenReturn(Optional.of(companyDTOList.get(0)));
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.add("name", "xavi2");
this.mockMvc.perform(patch("/companies/1")
.contentType(MediaType.APPLICATION_JSON)
.params(requestParams))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", Matchers.is("xavi2")));
}
}
Your problem is here
this.mockMvc.perform(patch("/companies/1")
.contentType(MediaType.APPLICATION_JSON)
.params(requestParams)) <---------------------
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", Matchers.is("xavi2")));
}
You pass the data as request parameters. But in your API you have #RequestBody meaning it expects to get the data in http request body and not as request parameters.
This is why you face 400 error meaning Bad Request which is caused by Spring having matched the URL path and also the http method type but something additional which was in the signature of API method was not provided in your request.
So you should use the .content(requestParams) method to set the content you want in the body of the request which you will send
Related documentation
Otherwise your API should have used #RequestParam instead of #RequestBody to receive the input as request parameters as previously sent from test.

Spring boot #MockMvcTest MockHttpServletResponse always returns empty body

I'm struggling with a simple spring boot rest controller test which always return empty body response.
Here is my test code looks like:
#WebMvcTest(AdminRestController.class)
#AutoConfigureMockMvc(addFilters = false)
public class PatientsUnitTest {
#Autowired
private MockMvc mvc;
#Autowired
private ObjectMapper objectMapper;
#MockBean
private PatientsService patientsService;
#MockBean
private TherapistsService therapistsService;
#MockBean
private TherapySchedulesService therapySchedulesService;
#Test
public void canAddPatient() throws Exception {
PatientsSaveRequestDto patientsSaveRequestDto = new PatientsSaveRequestDto();
patientsSaveRequestDto.setName("Sofia");
patientsSaveRequestDto.setPhone("01012345678");
Patients patient = patientsSaveRequestDto.toEntity();
when(patientsService.createPatient(patientsSaveRequestDto)).thenReturn(patient);
final ResultActions actions = mvc.perform(post("/admin/patient")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.characterEncoding(StandardCharsets.UTF_8.name())
.content(objectMapper.writeValueAsString(patientsSaveRequestDto)))
.andDo(print());
actions
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(jsonPath("name", is(patient.getName())))
.andDo(print());
}
My Controller:
#RestController
#RequiredArgsConstructor
public class AdminRestController {
private final PatientsService patientsService;
private final TherapistsService therapistsService;
private final TherapySchedulesService therapySchedulesService;
#PostMapping("/admin/patient")
#ResponseStatus(HttpStatus.OK)
#Operation(summary = "Create a patient")
public Patients cratePatient(
#RequestBody #Valid PatientsSaveRequestDto patientsSaveRequestDto
) {
return patientsService.createPatient(patientsSaveRequestDto);
}
// PatientsService
#Transactional
public Patients createPatient(PatientsSaveRequestDto patientsSaveRequestDto){
return patientsRepository.save(patientsSaveRequestDto.toEntity());
}
And this is the result of print():
MockHttpServletRequest:
HTTP Method = POST
Request URI = /admin/patient
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"53"]
Body = {"name":"sofia","phone":"01012345678","tel":null}
Session Attrs = {}
Handler:
Type = com.ussoft.dosu.web.controller.admin.AdminRestController
Method = com.ussoft.dosu.web.controller.admin.AdminRestController#cratePatient(PatientsSaveRequestDto)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
As you can see the request is sent correctly but the response values are all null.
When i test the same controller using #SpringBootTest with Rest Assured it works fine.
I'm using Spring boot 2.3.1, Junit5
Edit - added PatientsSaveRequestDto
#Getter
#Setter
#NoArgsConstructor
public class PatientsSaveRequestDto {
#NotBlank(message = "이름은 필수 입력사항입니다.")
private String name;
private String phone;
private String tel;
public Patients toEntity(){
return Patients.builder()
.name(name)
.phone(phone)
.tel(tel)
.build();
}
}
You need to provide equals method for PatientsSaveRequestDto.
When you execute a method on a mock, Mockito needs to check if any behaviour was specified for the arguments for which the method was called.
If the arguments match, the recorded result is returned,
If the arguments don't match, default value of the method return type is returned (null for all Objects, zero for numerics, false for bools)
You recorded the behaviour with the following call:
when(patientsService.createPatient(patientsSaveRequestDto)).thenReturn(patient);
This means that the actual argument to createPatient will be compared to patientsSaveRequestDto with equals.
Note that this behaviour can be changed by the use of ArgumentMatchers.
The patientsSaveRequestDto from your test and the actual argument to createPatient are not equal because:
you didn't define the equals method
they are different instances
thus, the inherited Object.equals returns false
You have 2 different instances because you created a #WebMvcTest.
The patientsSaveRequestDto you send to the controller is first serialized to String and then deserialized, and this is how a second instance got created.

How to know if a parameter value is provided in post request body in spring?

I am building web service with spring and come across with following problem.
There is a post service as follow.
#RequestMapping(value = "/postSomething", method = RequestMethod.POST)
public ResponseDTO postSomething(#RequestBody ADto aDto){
//post data
//return response
}
public class ADto{
private String firstParam;
private String secondParam;
// getter setter
}
So, my question is how can I know whether value of firstParam and secondParam is provided in request body or not.
RequestBody: { paramFirst: null, paramSecond: null}
Edit1:
Sorry for incomplete question:
For RequestBody: {paramFirst: first Value} and for above request value of paramSecond will be null.
So, how would I know whether paramSecond is included in request or not.
Edit2:
I don't want to validate. What I want to know is whether
request contains a particular parameter or not.
Because there are two different cases, one is value of a parameter is given null and other is paramter is not included in request.
You could use the #Valid annotation like so (pseudo code, didn't test it):
#RequestMapping(value = "/postSomething", method = RequestMethod.POST)
public ResponseDTO postSomething(#Valid #RequestBody ADto aDto){
// MethodArgumentNotValidException will be thrown if validation fails.
}
You'll need an exception handler to handle the validation error.
#ExceptionHandler
#ResponseBody
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public Error handleException(MethodArgumentNotValidException exception) {
//do something with the validation message: exception.getBindingResult()
}
And your class.
public class ADto{
#NotNull(message = "First parameter can not be null")
private String firstParam;
#NotNull(message = "Second parameter can not be null")
private String secondParam;
// getter setter
}
Try using Hibernate Validator (http://hibernate.org/validator/), it's really easy to integrate it with Spring.
That way, you'll need to annotate your Dto to enforce validation of required params and then call validate.
public class ADto{
#NotNull
private String firstParam;
#NotNull
private String secondParam;
// getter setter
}
#RequestMapping(value = "/postSomething", method = RequestMethod.POST)
public ResponseDTO postSomething(#RequestBody ADto aDto){
validator.validate(aDto)
//post data
//return response
}
You could make firstParam and secondParam type Optional:
ADto class
public class ADto {
private Optional<String> firstParam;
private Optional<String> secondParam;
// getter setter
}
postSomething method
#RequestMapping(value = "/postSomething", method = RequestMethod.POST)
public ResponseDTO postSomething(#RequestBody ADto aDto) {
if (Optional.ofNullable(aDto.getFirstParam()).isPresent()) {
// firstParam is provided in the request
} else {
// firstParam is not provided in the request
}
if (Optional.ofNullable(aDto.getSecondParam()).isPresent()) {
// secondtParam is provided in the request
} else {
// secondtParam is not provided in the request
}
}
Note that isPresent() will return false if and only if firstParam (as well as for secondParam) is not present in the request. Otherwise, even if the value is set to null, it will return true.

global binding initializer issues when multiple parameters with the same type exists in the form

I have the following setup:
#ControllerAdvice
public class GlobalBindingInitializer {
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(ZonedDateTime.class, new ZonedDateTimeBinder());
}
}
public class ZonedDateTimeBinder extends PropertyEditorSupport {
public void setAsText(String value) {
setValue(ZonedDateTime.parse(value));
}
public String getAsText() {
return ((ZonedDateTime) getValue()).toString();
}
}
#RequestMapping(value = "/bx", method = RequestMethod.POST)
public ResponseEntity<X> bx(#Valid #RequestBody MyForm form, BindingResult result) {
.............
}
public class AddNewBookingPeriodForm {
.....
#NotNull
private ZonedDateTime dateFrom;
#NotNull
private ZonedDateTime dateTo;
.....
}
#Test
public void test_bx_valid() throws Exception {
String content = "{" +
"\"uuid\":\""+...+"\"," +
"\"dateFrom\":\""+...+"\"," +
"\"dateTo\":\""+...+"\"," +
"\"ln\":\""+...+"\"," +
"\"fn\":\""+...+"\"," +
"\"p\":\""+...+"\"" +
"}";
mockMvc.perform(post("/b/bx")
.contentType("application/json;charset=UTF-8")
.content((content).getBytes()))
.andDo(print())
.andExpect(status().isCreated())
.andExpect(content().contentType("application/json;charset=UTF-8"))
.andExpect((jsonPath("$.", Matchers.hasSize(3))))
.andDo(print());
}
The problem is that when I run the test, it finds the controller handler method and says the it cannot read the request because of the body. I debugged the sent message and it has the required form, it's a valid JSON object. The problem is with the two ZonedDateTime fields.
I have a similar code in another place but the form used for validation has only one ZonedDateTime and it works. I think the problem may be because I have two in this form but I can't figure out what is happening.
I tried changing the type of dateFrom and dateTo from my form to String and everything worked just fine, so the problem is with those two.
Any ideas?
This debug message printed by the test:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /b/bx
Parameters = {}
Headers = {Content-Type=[application/json;charset=UTF-8]}
Handler:
Type = valid handler controller
Method = valid handler method
Async:
Async started = false
Async result = null
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 = []
EDIT 1
Here is a valid sample
{
"uuid":"e50b5cbf-c81a-40de-9eee-ceecd21ad179",
"dateFrom":"2015-09-07T19:25:42+03:00",
"dateTo":"2015-09-08T19:25:42+03:00",
"ln":"ln",
"fn":"fn",
"p":"007"
}
I think the problem may be coming from #RequestBody annotation because in the working case I talked about in the previous rows I use the GET method without the #RequestBody annotation and the mapping works just fine.
Given RequestBody, Spring will use an HttpMessageConverter to deserialize your request body into an instance of your given type. In this case, it will use MappingJackson2HttpMessageConverter for the JSON. This converter does not involve your PropertyEditorSupport at all.
To deserialize ZonedDateTime correctly, you'll to register an appropriate Jackson module for mapping java.time types. One possibility is jackson-datatype-jsr310. I believe it should be registered automatically when found on the classpath. If it's not, you'll need to register it manually by creating and registering a MappingJackson2HttpMessageConverter manually with an appropriately created ObjectMapper.

How to avoid NullPointerException in test used MockMvc?

I have controller:
#Controller
public class EventMenuController{
#RequestMapping(value = "/updateEvent", method = RequestMethod.POST)
public String updateEvent(Model model,
#Valid #ModelAttribute("existedEvent") Event event,
BindingResult result,
#ModelAttribute("linkedCandidates") Set<Candidate> candidates,
#ModelAttribute("linkedvacancies") Set<Vacancy> vacancies,
#RequestParam(required = true, value = "selectedEventStatusId")Integer EventStatusId,
#RequestParam(required = true, value = "selectedEventTypeId")Integer EventTypeId ,
RedirectAttributes attributes) {
if (result.hasErrors()) {
//model.addAttribute("idEvent", event.getId());
event.setCandidates(candidates);
event.setVacancies(vacancies);
return "eventDetails";
}
eventService.updateEventAndLinkedEntities(event, candidates, vacancies ,EventTypeId,EventStatusId);
attributes.addAttribute("idEvent",event.getId() );//event is null therefore NPE here
attributes.addAttribute("message", "submitted correctly at "+new Date());
return "redirect:eventDetails";
}
}
For testing this method I wrote following class:
#ContextConfiguration(locations = { "classpath:/test/BeanConfigUI.xml" })
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
public class EventMenuControllerTest {
#Test
public void updateEvent() throws Exception{
MockHttpServletRequestBuilder request = MockMvcRequestBuilders
.post("/updateEvent");
request.param("selectedEventStatusId", "1");
request.param("selectedEventTypeId", "1");
EventMenuController eventMenuController = (EventMenuController) wac.getBean("eventMenuController");
EventService mockEventService = Mockito.mock(EventService.class);
eventMenuController.eventService = mockEventService;
Mockito.doNothing().when(mockEventService).updateEventAndLinkedEntities(any(Event.class), any(Set.class),any(Set.class), any(Integer.class), any(Integer.class));
ResultActions result = mockMvc.perform(request);
result.andExpect(MockMvcResultMatchers.view().name("redirect:eventDetails"));
result.andExpect(MockMvcResultMatchers.model().attributeExists("idEvent"));
result.andExpect(MockMvcResultMatchers.model().attributeExists("message"));
}
}
In the process request executing on server side I see error that show me that event object is null.
Question:
What request I must write that pass event to server-side(controller method) using MockMvc ?
The Event class object is not initialized. You can either create an Event object or create a mock of an Event object depending on your test case and send it to the EventMenuController class object. You can do this similar to how you have sent a mock object of EventService to EventMenuController.
It is better practice to use fields as part of a class and not as part of a method. This will give you flexibility to mock any field.

Categories

Resources