spring MockMvc testing for model attribute - java

I have a controller method for which i have to write a junit test
#RequestMapping(value = "/new", method = RequestMethod.GET)
public ModelAndView getNewView(Model model) {
EmployeeForm form = new EmployeeForm()
Client client = (Client) model.asMap().get("currentClient");
form.setClientId(client.getId());
model.addAttribute("employeeForm", form);
return new ModelAndView(CREATE_VIEW, model.asMap());
}
Junit test using spring mockMVC
#Test
public void getNewView() throws Exception {
this.mockMvc.perform(get("/new")).andExpect(status().isOk()).andExpect(model().attributeExists("employeeForm")
.andExpect(view().name("/new"));
}
I am getting NullPointerException as model.asMap().get("currentClient"); is returning null when the test is run, how do i set that value using spring mockmvc framework

As an easy work around you should use MockHttpServletRequestBuilder.flashAttr() in your test:
#Test
public void getNewView() throws Exception {
Client client = new Client(); // or use a mock
this.mockMvc.perform(get("/new").flashAttr("currentClient", client))
.andExpect(status().isOk())
.andExpect(model().attributeExists("employeeForm"))
.andExpect(view().name("/new"));
}

The response is given as string chain (I guess json format, as it is the usual rest service response), and thus you can access the response string via the resulting response in this way:
ResultActions result = mockMvc.perform(get("/new"));
MvcResult mvcResult = result.andExpect(status().isOk()).andReturn();
String jsonResponse = mvcResult.getResponse().getContentAsString();
And then you can access to the response via getResponse().getContentAsString(). If json/xml, parse it as an object again and check the results. The following code simply ensures the json contains string chain "employeeForm" (using asertJ - which I recommend)
assertThat(mvcResult.getResponse().getContentAsString()).contains("employeeForm")
Hope it helps...

Related

How to test a post request which returns a URI with spring boot, junit 5 and mockito

I'm creating a REST API in spring boot where I have a post request end point. If it succeeds it returns the URI of the newly created resource in the location header.
I wanted to write tests for the endpoint. I'm using JUnit v5, and Mockito.
The problem is I have mocked the call to the service function but it still returns a NullPointerException
In the controller class, in the post end point I have this function:
#PostMapping(value = "/new")
public ResponseEntity<String> registerNewDoctor(#RequestBody #Valid DoctorPOJO doctor) {
Doctor savedDoctor = doctorService.addNewDoctor(doctor);
if (doctor != null) {
log.debug("DOCTOR SAVED: {}",savedDoctor);
URI location = ServletUriComponentsBuilder.fromCurrentContextPath().path("/id/{id}").buildAndExpand(savedDoctor.getDocId()).toUri();
return ResponseEntity.created(location).build();
} else {
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body("Object can not be processed");
}
}
In the unit Test I have:
#WebMvcTest
#Slf4j
class DoctorControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#MockBean
private DoctorService doctorService;
#MockBean
private SlotService slotService;
#Test
void registerNewDoctorTest() throws Exception {
// given - set up
SlotPOJO slot1 = new SlotPOJO("monday", LocalTime.parse("09:00:00.00"), LocalTime.parse("13:00:00.00"), LocalTime.parse("00:15:00.00"));
SlotPOJO slot2 = new SlotPOJO("tuesday", LocalTime.parse("09:00:00.00"), LocalTime.parse("13:00:00.00"), LocalTime.parse("00:15:00.00"));
List<SlotPOJO> slotPOJOList = new ArrayList<>(Arrays.asList(slot1, slot2));
DoctorPOJO doctorPOJO = new DoctorPOJO(1, "doctor1", "doc1email#gmail.com", "1234567890", "123", "MBBS", "Orthopedics", "4y", slotPOJOList);
Doctor doc = new Doctor(1, "doctor1", "doc1email#gmail.com", "1234567890", "123", "MBBS", "Orthopedics", "4y");
// mocking the service method
given(doctorService.addNewDoctor(doctorPOJO)).willReturn(doc);
// when - perform actions to be tested
ResultActions response = mockMvc.perform(post("/doctor/new").contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(doctorPOJO)).accept(MediaType.APPLICATION_JSON));
// then - verify the results
response.andDo(print()).andExpect(status().isCreated());
}
}
As you can see I have mocked the addNewDoctor method using mockito to return doc when called but I still get this error:
Request processing failed; nested exception is
java.lang.NullPointerException: Cannot invoke
"project.doctorservice.entity.Doctor.getDocId()" because "savedDoctor"
is null
Please help me sort out this mistake and guide me a little to how to do it correctly because I'm new to unit testing

How to mock rest template exchange

I have a method in which is it using RestTemplate. I using the following code to make a call:
final ResponseEntity<RESTResponse> responseEntity = restTemplate.exchange(uri,
HttpMethod.POST,
httpEntityWithHeaders,
RESTResponse.class);
httpEntityWithHeads is of type HttpEntity<String>. I am writing a test and trying to mock RestTemplate so that when it calls the exchange method, it will throw an exception.
I am trying to mock it like this:
when(restTemplate.exchange(
ArgumentMatchers.contains(randomHost),
ArgumentMatchers.eq(HttpMethod.POST),
ArgumentMatchers.<HttpEntity<List<String>>>any(),
ArgumentMatchers.<ParameterizedTypeReference<List<RESTResponse>>>any())
).thenThrow(new ResourceAccessException("Random exception message."));
But when running the test, it doesn't throw the exception, it just continues.
Any suggestions?
As you said httpEntityWithHeads is of type HttpEntity<String>, so you have to stub in way that matches to HttpEntity<String>
when(restTemplate.exchange(
ArgumentMatchers.contains(randomHost),
ArgumentMatchers.eq(HttpMethod.POST),
ArgumentMatchers.<HttpEntity<String>>any(),
ArgumentMatchers.<ParameterizedTypeReference<List<RESTResponse>>>any())
).thenThrow(new ResourceAccessException("Random exception message."));
To me seems that your last parameter is not a list it is a class, and that is why the stub is failing, I tried the following and it is working.
#Test(expected = IllegalArgumentException.class)
public void test() {
RestTemplate restTemplate = mock(RestTemplate.class);
when(restTemplate.exchange(anyString(), ArgumentMatchers.eq(HttpMethod.POST),
any(HttpEntity.class),
any(Class.class))).thenThrow(new IllegalArgumentException("a"));
Rest rest = new Rest(restTemplate);
rest.call();
}
public void call(){
HttpEntity<Object> httpEntityWithHeaders= new HttpEntity<>(null);
final ResponseEntity<Object> responseEntity = restTemplate.exchange("a",
HttpMethod.POST,
httpEntityWithHeaders,
Object.class);
}

MvcResult has a status of 200 including the response but the assertion fails

I am currently writing a JUnit test case for a controller in my application which returns a object (a URL). I am trying to assert the expected and the actual URL to be the same. There are 2 things happening here when I inspect the MvcResult result:
mockResponse has a status code of 200.
In ModelAndView, the model does have the expected url value but when I try to assert the result using result.getResponse().getContentAsString(),
the assertion fails as the result is empty.
What I have already tried:
While debugging, I see the control moving to the service which means that the values were properly mocked and the expected url got returned to the result (as it was present in the ModelAndView when inspected).
I have tried to give the expected url as a json object, used object mapper to read it and then tried a JSONAssert but the result is still empty.
#RunWith(SpringJUnit4ClassRunner.class)
public class StudentControllerTest {
private static final String CACHE_URL= "cacheurl";
#Mock
StudentCacheService studentCacheService;
#InjectMocks
StudentCacheController studentCacheController;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(studentCacheController).build();
}
#Test
public void testGetScoresUrl() throws Exception {
Mockito.when(studentCacheService.getStudentUrl("123", "science"))
.thenReturn(new StudentUrl(CACHE_URL));
MvcResult result = this.mockMvc.perform(MockMvcRequestBuilders.get("/student/123/scores")
.header("subject", "science").contentType(MediaType.APPLICATION_JSON)).andExpect(status().is2xxSuccessful())
.andReturn();
Assert.assertEquals(CACHE_URL, result.getResponse().getContentAsString());
}
}
My Controller class is as below:
#Controller
#RequestMapping("/student")
public class StudentCacheController {
#Autowired
StudentCacheService studentCacheService;
#GetMapping(path = "/{studentId}/scores",produces = MediaType.APPLICATION_JSON_VALUE)
public StudentUrl getScores(#PathVariable String studentId, #RequestHeader(value = "subject", required = true) String subject) throws Exception {
return studentCacheService.getStudentUrl(studentId, subject);
}
}
The response is as below:
MockHttpServletResponse:
Status = 200
Error message = null
Forwarded URL = student/123/scores
Included URL = []
ModelAndView:
model = ModelMap
key = studentUrl
value = StudentUrl
url = "cacheurl"
I am receiving this error : org.junit.ComparisonFailure: expected:<[cacheurl]> but was:<[]>
Any help appreciated. Thanks!

java.lang.AssertionError: Content type not set even after setting content type as json/application

This question has been asked before and I have tried their solution but that doesn't work for me, I am using MockMvc to unit test content type of my rest call.
I am getting this exception:
java.lang.AssertionError: Content type not set
While I'm setting it in my search method using produces attribute.
This is the method where I am initializing the mocks:
#Before
public void init() {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(restController, "luceneSearchEnabled", true);
mockMvc = standaloneSetup(restController).build();
}
This is my test method:
#Test
public void pmmSearchContentTypeTest() throws Exception {
mockMvc
.perform(get("/api/v1/pmm").contentType(MediaType.APPLICATION_JSON))
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON_VALUE)
.andReturn();
}
This is my search method where I am setting content type:
#RequestMapping(value = "/api/" + REST_API_VERSION + "/" + ONE_INTERFACE, method = RequestMethod.GET, produces ={MediaType.APPLICATION_JSON_VALUE})
#ResponseBody
public String pmmSearch() { ... }
I don't know what is wrong here.
I faced the same error and found that , the mock service for this controller method returns null. change mock service method to return values for any() input and test this to get rid of this error.
when(service.method(any())).thenReturn(someElement);
someElement was null earlier causing this error case
Figured it out myself
Instead of using the mock object of retcontroller here
mockMvc = standaloneSetup(restController).build();
I had to use a real object
mockMvc = standaloneSetup(new RestController()).build();
and in order to avoid spring validation error I had to use complete path here
mockMvc
.perform(get("/api/v1/pmm/search{}").contentType(MediaType.APPLICATION_JSON))
If the response returns null, you can get that error, as it says #stuti-verma.
It happened to me right now.
The case: In my test, I was sending a json to the controller, but the object that I used to generate the mocked response didn't have the equals/hashCode implemented. So, it will never match with the received json.
#RequestBody anObject
mock.theMethod(anObject).thenReturn(theResponse)
anObject must have equals/hashCode to be able to be compared
In my case, I had to set the content type returned by the request in the ResponseEntity itself:
PaymentResponse paymentResponse = makePaymentService.makePayment(paymentRequest);
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json");
return new ResponseEntity<>(paymentResponse, HttpStatus.OK);
see java.lang.AssertionError: Content Type Not Set - Spring Controller JUnit Tests

How to check String in response body with mockMvc

I have simple integration test
#Test
public void shouldReturnErrorMessageToAdminWhenCreatingUserWithUsedUserName() throws Exception {
mockMvc.perform(post("/api/users").header("Authorization", base64ForTestUser).contentType(MediaType.APPLICATION_JSON)
.content("{\"userName\":\"testUserDetails\",\"firstName\":\"xxx\",\"lastName\":\"xxx\",\"password\":\"xxx\"}"))
.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(?);
}
In last line I want to compare string received in response body to expected string
And in response I get:
MockHttpServletResponse:
Status = 400
Error message = null
Headers = {Content-Type=[application/json]}
Content type = application/json
Body = "Username already taken"
Forwarded URL = null
Redirected URL = null
Tried some tricks with content(), body() but nothing worked.
You can call andReturn() and use the returned MvcResult object to get the content as a String.
See below:
MvcResult result = mockMvc.perform(post("/api/users").header("Authorization", base64ForTestUser).contentType(MediaType.APPLICATION_JSON)
.content("{\"userName\":\"testUserDetails\",\"firstName\":\"xxx\",\"lastName\":\"xxx\",\"password\":\"xxx\"}"))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isBadRequest())
.andReturn();
String content = result.getResponse().getContentAsString();
// do what you will
#Sotirios Delimanolis answer do the job however I was looking for comparing strings within this mockMvc assertion
So here it is
.andExpect(content().string("\"Username already taken - please try with different username\""));
Of course my assertion fail:
java.lang.AssertionError: Response content expected:
<"Username already taken - please try with different username"> but was:<"Something gone wrong">
because:
MockHttpServletResponse:
Body = "Something gone wrong"
So this is proof that it works!
Spring MockMvc now has direct support for JSON. So you just say:
.andExpect(content().json("{'message':'ok'}"));
and unlike string comparison, it will say something like "missing field xyz" or "message Expected 'ok' got 'nok'.
This method was introduced in Spring 4.1.
Reading these answers, I can see a lot relating to Spring version 4.x, I am using version 3.2.0 for various reasons. So things like json support straight from the content() is not possible.
I found that using MockMvcResultMatchers.jsonPath is really easy and works a treat. Here is an example testing a post method.
The bonus with this solution is that you're still matching on attributes, not relying on full json string comparisons.
(Using org.springframework.test.web.servlet.result.MockMvcResultMatchers)
String expectedData = "some value";
mockMvc.perform(post("/endPoint")
.contentType(MediaType.APPLICATION_JSON)
.content(mockRequestBodyAsString.getBytes()))
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.data").value(expectedData));
The request body was just a json string, which you can easily load from a real json mock data file if you wanted, but I didnt include that here as it would have deviated from the question.
The actual json returned would have looked like this:
{
"data":"some value"
}
Taken from spring's tutorial
mockMvc.perform(get("/" + userName + "/bookmarks/"
+ this.bookmarkList.get(0).getId()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.id", is(this.bookmarkList.get(0).getId().intValue())))
.andExpect(jsonPath("$.uri", is("http://bookmark.com/1/" + userName)))
.andExpect(jsonPath("$.description", is("A description")));
is is available from import static org.hamcrest.Matchers.*;
jsonPath is available from import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
and jsonPath reference can be found here
Spring security's #WithMockUser and hamcrest's containsString matcher makes for a simple and elegant solution:
#Test
#WithMockUser(roles = "USER")
public void loginWithRoleUserThenExpectUserSpecificContent() throws Exception {
mockMvc.perform(get("/index"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("This content is only shown to users.")));
}
More examples on github
here a more elegant way
mockMvc.perform(post("/retrieve?page=1&countReg=999999")
.header("Authorization", "Bearer " + validToken))
.andExpect(status().isOk())
.andExpect(content().string(containsString("regCount")));
One possible approach is to simply include gson dependency:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
and parse the value to make your verifications:
#RunWith(SpringRunner.class)
#WebMvcTest(HelloController.class)
public class HelloControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private HelloService helloService;
#Before
public void before() {
Mockito.when(helloService.message()).thenReturn("hello world!");
}
#Test
public void testMessage() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON_VALUE))
.andReturn();
String responseBody = mvcResult.getResponse().getContentAsString();
ResponseDto responseDto
= new Gson().fromJson(responseBody, ResponseDto.class);
Assertions.assertThat(responseDto.message).isEqualTo("hello world!");
}
}
Here is an example how to parse JSON response and even how to send a request with a bean in JSON form:
#Autowired
protected MockMvc mvc;
private static final ObjectMapper MAPPER = new ObjectMapper()
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(new JavaTimeModule());
public static String requestBody(Object request) {
try {
return MAPPER.writeValueAsString(request);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
public static <T> T parseResponse(MvcResult result, Class<T> responseClass) {
try {
String contentAsString = result.getResponse().getContentAsString();
return MAPPER.readValue(contentAsString, responseClass);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Test
public void testUpdate() {
Book book = new Book();
book.setTitle("1984");
book.setAuthor("Orwell");
MvcResult requestResult = mvc.perform(post("http://example.com/book/")
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody(book)))
.andExpect(status().isOk())
.andReturn();
UpdateBookResponse updateBookResponse = parseResponse(requestResult, UpdateBookResponse.class);
assertEquals("1984", updateBookResponse.getTitle());
assertEquals("Orwell", updateBookResponse.getAuthor());
}
As you can see here the Book is a request DTO and the UpdateBookResponse is a response object parsed from JSON. You may want to change the Jackson's ObjectMapper configuration.
Another option is:
when:
def response = mockMvc.perform(
get('/path/to/api')
.header("Content-Type", "application/json"))
then:
response.andExpect(status().isOk())
response.andReturn().getResponse().getContentAsString() == "what you expect"
You can use getContentAsString method to get the response data as string.
String payload = "....";
String apiToTest = "....";
MvcResult mvcResult = mockMvc.
perform(post(apiToTest).
content(payload).
contentType(MediaType.APPLICATION_JSON)).
andReturn();
String responseData = mvcResult.getResponse().getContentAsString();
You can refer this link for test application.
String body = mockMvc.perform(bla... bla).andReturn().getResolvedException().getMessage()
This should give you the body of the response. "Username already taken" in your case.
Another example is:
.andExpect(jsonPath("$").value(containsString("You have successfully deleted")));
The body response:
Body = You have successfully deleted a [Object] with ID: 1
Here is a more production ready way of doing it where you if you may have big json responses then you do not have to clutter your test files with json strings, just load them from static Resources folder and assert them directly.
#Test
#DisplayName("invalid fields")
void invalidfields() throws Exception {
String request = getResourceFileAsString("test-data/http-request/invalid-fields.json");
String response_file_path = "test-data/http-response/error-messages/invalid-fields.json";
String expected_response = getResourceFileAsString(response_file_path);
mockMvc.perform(evaluateRulesOnData(TRACKING_ID.toString(), request))
.andExpect(status().isBadRequest())
.andExpect(content().json(expected_response));
}
helper function to load test files from classpath
public static String getResourceFileAsString(String fileName) throws IOException {
Resource resource = new ClassPathResource(fileName);
File file = resource.getFile();
return new String(Files.readAllBytes(file.toPath()));
}
The expected response has an array with many elements in the list which are matched despite being in random order during each test run.

Categories

Resources