How to test spring's #RequestBody which recieves custom object using MockMvc - java

I am trying to test my controller method which takes input parameter as One of the objects in my application.I know how to test with String as input parameter,but not with customized object's.
Below is the code in my app
#RequestMapping(value = "/someUrl", method = RequestMethod.POST)
public ResponseEntity<?> save(#RequestBody Transaction transaction) {
I thought below test code is one of the ways to test.but it's failing
#Test
public void test() throws Exception {
Transaction Transaction = new Transaction();
Gson gson = new Gson();
String json = gson.toJson(transaction);
mockMvc.perform(post("/someUrl")
.contentType(MediaType.APPLICATION_JSON)
.content(json));
When I run the above test,I am getting error saying "Actually, there were zero interactions with this mock".
Could you please help me on how to pass Customized object to MockMvc post method.

mapper.writeValueAsString(json) is a better approach to do it

Related

MockMvc can't validate with param #DateValid

I'm using MockMvc to test my method that has a date parameter. The parameter is annotated with #DateValid but MockMvc doesn't trigger the validator and returns success when I input a wrong date.
StudentControler
public ResponseEntity<?> getStudents(#PathVariable("id") String studentId,
#RequestParam("birthDate") #DateValid String Date) {
//do something
}
Test
public class StudentController extends AbstractControllerTest {
#Test
public void testStudents_validRequest() throws Exception {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("birthDate","aaaa");
MvcResult result = getMvc().perform(get("/student/{studentId}", "test")
.params(params)
.characterEncoding(StandardCharsets.UTF_8.name())
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(mapToJson(studentResDto)))
.andReturn();
verify(studentService).getStudentId(anyString(), anyString());
log(log, result);
}
}
I do not understand why because when I test using Postman it works as expected. Postman does validate but MockMvc does not?
I using Spring-Boot 2.1.3
I can think of 2 problems here:
The validation annotation class is not loaded properly in your test. This seems to happen primarily when combining Spring validation with Hibernate validation (see also this question on how to fix that.
Your test is not setup properly. Could it be that you are not fully loading your controller in your mock test? Perhaps you are mocking the controller accidentally?

How to unit test a Controller method that has a RequestParam of an object?

I have a controller mapping
#RequestMapping(value = "/something", method = RequestMethod.GET)
public String get(#RequestParam("id") Person aPerson, Model aModel) {
aModel.addAttribute("person", aPerson);
return "index";
}
How do I go about testing this through MockMvc?
I can do something like this
mockMvc.perform(get("/something?id=1")).andExpect(status().is2xxSuccessful());
But this will not work since the RequestParam is an object, not a String. The conversion is done by Spring but I'm unit testing the method and do not want to start up the application context. How do I unit test something like this with MockMvc?
you may try something like
#Test
public void test() throws Exception {
Model model= new Model();
mockMvc.perform(MockMvcRequestBuilders.get("/something?id=1")
.content(asJsonString(model))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)).andExpect(status().is2xxSuccessful());
}

Spring controller test fails with 404 when #PathVariable is a string

I am trying to test a Spring controller method - the method is at the end of the post and the test class below that. I've stripped it back a bit to try and narrow down the problem.
When I run the test as is it fails:
java.lang.AssertionError: Status
Expected :200
Actual :404
If I edit the mockMvc.perform as follows then the test passes, I don't even have to change the #PathVariables in the controller to be Longs:
mockMvc.perform(get(ApplicationEndPoints.GET_CCS_NAME_AND_ADDRESS_AJAX, 1L, 2L))
.andExpect(status().isOk());
The controller method itself works fine and returns JSON as expected. Can I just use these Long values and expect the test to be ok or how can I get it to work with Strings?
I should add that I'm a total testing noob. Thanks!
controller method:
#PreAuthorize("hasAuthority('auditor')")
#RequestMapping(value = ApplicationEndPoints.GET_APPLICANT_DATA, method = RequestMethod.GET)
#ResponseBody
public ApplicantData getNameAndAddress(#PathVariable("businessId") String businessId, #PathVariable("date") String date) {
//Date d = Date.valueOf(date);
ApplicantParams params = new ApplicantParams();
//params.setBusinessId(businessId);
//params.setApplicationReceivedDate(d);
params.setRoleId(ADDRESS_ROLE.HR.getRoleId());
return applicantService.getApplicantData(params);
}
test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {ApplicationTestConfig.class})
#WebAppConfiguration
public class ClientDetailAjaxControllerTest {
#InjectMocks
private ClientDetailAjaxController clientDetailAjaxController;
private MockMvc mockMvc;
private ApplicantServiceInterface<Applicant> applicantService = Mockito.mock(ApplicantServiceImpl.class, Mockito.RETURNS_DEEP_STUBS);
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(clientDetailAjaxController).build();
}
#Test
public void getNameAndAddress() throws Exception {
Mockito.when(applicantService.getApplicantData(Mockito.any(ApplicantParams.class)))
.thenReturn(ApplicationTestData.getApplicantData());
mockMvc.perform(get(ApplicationEndPoints.GET_APPLICANT_DATA, Mockito.anyString(), Mockito.anyString()))
.andExpect(status().isOk());
Mockito.verify(applicantService, Mockito.times(1)).getApplicantData(Mockito.any(ApplicantParams.class));
Mockito.verifyNoMoreInteractions(applicantService);
}
}
EDIT: I have cleared up one or two things in response to comments...
404 tells you that mockMVC cannot match the controller method you're trying to call.
I'm noticing that in your controller, the path is ApplicationEndPoints.GET_APPLICANT_DATA, while in the test you've shown the path is ApplicationEndPoints.GET_CCS_NAME_AND_ADDRESS_AJAX.
Nevertheless - don't use Mockito.anyString() in place of parameter values you have to supply to your RequestBuilder in the mockMVC perform method.
Here's an idea how code should look like (derived from your example):
To test this controller method:
#RequestMapping(value = "/applicant/name-and-address/", method = RequestMethod.GET)
public ApplicantData getNameAndAddress(#PathVariable("businessId") String businessId, #PathVariable("date") String date) {
// some code
}
You need to do something like:
mockMvc.perform(get("/applicant/name-and-address/{businessId}/{date}", "myBusinessId", "myDateAsString")).andExpect(status().isOk());
The difference is that you have to pass actual string values, not Mockito.anyString().
Some other notes:
If your URL path variable has the same name as the parameter you're binding it to, you can write #PathVariable String id, instead of #PathVariable("id") String id.
you can use #GetMapping in place of #RequestMapping for GET method, #PostMapping for POST etc.

How do you create a unit test for Spring MVC endpoint containing a required request parameter that contains a JSON string?

I understand that it is not good practice to have a request parameter that contains a JSON string but I have inherited this code
so I have to work with it at the moment.
I am trying to create a unit test to test this endpoint but I am having an issue with the item request parameter:
The controller snippet is:
#RequestMapping(value = "/item/{idtype}:{id}/details",
method = RequestMethod.GET, produces="application/json; charset=UTF-8")
#ApiOperation(value = "",
response = ItemDTO.class, responseContainer = "List")
#ApiImplicitParams({#ApiImplicitParam(name = "Authorization", value = "Authorization token",
required = false, dataType = "string", paramType = "header")})
public Collection<ItemDTO> getItemDetailList(#PathVariable("idtype") String idType, #PathVariable("id") String id,
#RequestParam(value = "item", required = true) ItemDTO item {
...
}
...
#InitBinder("item")
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(ItemDTO.class, new StringArrayPropertyEditor(null));
}
The binder is used to convert the JSON string passed in the item parameter, to the ItemDTO java object.
The unit test snippet is:
private MockMvc mvc;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(ItemDetailsController).build();
}
...
StringBuilder stringBuilder = new StringBuilder("/v2/item/").append("type").append(":").append("1234")
.append("/details");
Map<String, String> value = new HashMap<>();
value.put("value", "{\"itemId\":\"test\"}");
URI uri = new URI(stringBuilder.toString());
UriComponents uriComponents = UriComponentsBuilder.fromUri(uri).query("item={value}").buildAndExpand(value);
mvc.perform(MockMvcRequestBuilders.get(uriComponents.toUri())
.contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(MockMvcResultMatchers.status().isOk()
When I run the unit test I get the exception:
java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [biz.dto.ItemDTO]: no matching editors or conversion strategy found
I have tried other permutations and combinations, such as using chaining the .param method to the .perform method of mvc to add the item parameter or just simply adding the item
parameter to the Stringbuilder but I have not had any success. I have also tried searching the forum for similar questions but I have had no success there either.
Any help would be greatly appreciated.
you are messing up your tests.
if you want to test binder - test binder. (mock test) if you want to test controller - test it. (mock test, again) What you want is full integration test, to see that binder is used by annotation and then parses string (json) into proper class. for that I would recommend RestAssured or something

A spring integration test

In my integration test, I tried to use resttemplate to send a Get request to a dummy server created by MockMvcBuilders. However I got an error:
I/O error on GET request for "http://localhost:8080/test":Connection refused:
(In the function testAccess(), url is "http://localhost:8080/test"). My code is as below:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#IntegrationTest("server.port=8080")
public class MyTest {
private MockMvc mockMvc = null;
#Autowired
private WebApplicationContext context;
#Value("${server.port}")
private int port;
#Autowired
private MyController myController;
#Before
public void setUp(){
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.build();
}
#Test
public void testAccess() throws Exception{
RestTemplate restTemplate=new RestTemplate();
String url="http://localhost:8080/test";
try{
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
}
catch(ResourceAccessException ex){
System.out.println(ex.getMessage());
}
}
#Controller
public static class MyController {
#RequestMapping(value = "/test", method = RequestMethod.GET)
public #ResponseBody String access() {
return "OK";
}
}
}
The way I've done it is this:
First, you create a mock server from the actual RestTemplate you are using in your application
#Before
public void setup() throws Exception {
mockServer = MockRestServiceServer.createServer(myService.restTemplate);
}
Then you define how that request is going to work:
mockServer.expect(requestTo("http://localhost:8080/myrestapi"))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess("{ success: true }", MediaType.APPLICATION_JSON));
And last you call the method in your application that will trigger a call to that url with that RestTemplate:
#Test
public void testThis() throws Exception {
myService.somethingThatCallsMyRestApi(parameters);
}
That will make your tests work as if there was a server up and running to process requests.
Using this in your example makes no sense, cause you would be testing that you build your test correctly and nothing else from the actual application.
The problem with this is that you cannot test dynamic responses. I mean, in my case the method I'm calling generates different data every time you call it and then sends it to the mockServer and then validates that the response matches in some very specific way. I haven't found a solution yet, but if the data you are going to send and receive is previously known, which would be in most cases, you'll have no problem using this.
Why are you defining a controller in your Test class and then trying to test it ? It doesn't feel logical to try to test something that is defined within the test it self.
Rather you would want to test a controller defined somewhere outside your tests, an actual controller that is used within your application.
Let's say MyController is defined as an actual controller then you could use the mockMvc object you created to test it.
mockMvc.perform(get('/test'))
.andExpect(status().isOk())

Categories