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.
Related
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).
I was having some problem when trying to access session attribute in Controller. I declared my session attributes as such:
#Controller
#SessionAttributes({ WebKeys.OBJECT_SIX, WebKeys.DSP_LOGIC, WebKeys.NEW_CARD_FORM })
In each of my API, I am calling the function:
#RequestMapping(value = "/apiA.do", method = RequestMethod.POST)
public String doAPIa(Model model) {
setInfo(model);
}
#RequestMapping(value = "/apiB.do", method = RequestMethod.POST)
public String doAPIb(Model model) {
setInfo(model);
}
In my setInfo(), I am trying to access the session attribute and add value back to the model:
private void setInfo(Model model) throws Exception{
String populationId = // need to get from WebKeys.OBJECT_SIX session attribute
if(populationId!=null && (populationId.equals(Constants.POP_TYPE_ID))){
DisplayHelperTO helper = (DisplayHelperTO) // need to get from WebKeys.DSP_LOGIC;
NewCardNewBasicForm newCardForm = (NewCardNewBasicForm ) // need to get from WebKeys.NEW_CARD_FORM);
model.addAttribute("newCardForm", newCardForm);
model.addAttribute("dspLogic", helper);
}
}
I tried to declare in such way:
private void setInfo(Model model,
#SessionAttribute(WebKeys.OBJECT_SIX) String populationId) throws Exception{
}
However, if I am declaring the function in this way, the part to call the setInfo() in both doAPIa and doAPIb will be highlighted in syntax error. Any ideas on how to access the session attributes in function? Thanks!
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 utilize specific argument in spring validator and I cannot merge arguments into a single object?
Say I have a controller like,
#RequestMapping(value = "url",
consumes = { "application/json" }, produces = { "application/json" },
method = RequestMethod.POST)
public MyResponse getMyData(
#Valid #RequestBody MyRequest myRequest, Errors errors, CustomObject obj){
return new MyResponse();
}
I have a aspect implementation for updating the CustomObject
#Around("#annotation(MyAnnotation) && execution(* MyController.getMyData(..))")
public Object requestsAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
Object value = null;
CustomObject obj = new CustomObject();
try {
Object[] args = proceedingJoinPoint.getArgs();
//populate CustomObject
obj = (CustomObject)args[2];
obj.setField("value");
value = proceedingJoinPoint.proceed(args);
} catch (Throwable e) {
e.printStackTrace();
}
return value;
}
CustomObject's updated value I'm able to get in the controller. Now i need to validate MyRequest also using a spring custom validator like,
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new MyCustomValidator());
}
And MyCustomValidator's 'validate()' function is as,
#Override
public void validate(Object target, Errors errors) {
validate((MyRequest)target);
}
Both aspect and validation are working separately but when both are integrated the validation is failing because target in validate method considers 'CustomObject' for validation rather than using 'MyRequest'. Validate method will accept only two arguments while 'getMyData()' method in controller has three. How to resolve this or is there any other approach to achieve a similar solution?
You can try to assign the name of the object to validate to the initBinder (use the name of the object instance, not the class).
InitBinder("myRequest")
protected void initBinder(WebDataBinder binder) {
...
If it's not working, you can try to give an order to the aspect using the Order annotation on it or implement the Order interface.
Anyway, I'm not totally confident about the correctness of the first two options because I cannot test the code (I answer from a tablet). So, I suggest also a third option, where you put the validation inside the controller
#RequestMapping(value = "url",
consumes = { "application/json" },
produces = {"application/json" },
method = RequestMethod.POST)
public MyResponse getMyData(
#RequestBody MyRequest myRequest, BindingResult result, CustomObject obj){
MyCustomValidator validator = MyCustomValidator();
validator.validate(myRequest, result);
if (result.hasErrors()){
// do something
}
return new MyResponse();
}
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.