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.
Related
I am trying to add parameters to a postForEntity request but it seems to never go through. Here is the minimum reproducible code:
#Test
public void test()
{
String urlTemplate = UriComponentsBuilder.fromHttpUrl("http://localhost:8080/test")
.queryParam("update")
// .queryParam("update", "{update}") //This does not work either
.encode()
.toUriString();
HashMap<String, String> paramValues = new HashMap<>();
paramValues.put("update", "true");
HttpEntity<AnimateRequest> httpEntity = new HttpEntity<>(null, new HttpHeaders());
ResponseEntity<Boolean> response = this.template.postForEntity(
urlTemplate,
httpEntity,
Boolean.class,
paramValues);
boolean bb = response.getBody();
}
In a controller:
#PostMapping(value = "/test")
public ResponseEntity<Boolean> tester(#RequestParam(name="update", required = false) boolean rr)
{
return ResponseEntity
.ok()
.contentType(MediaType.TEXT_PLAIN)
.body(rr);
}
Errors with:
org.springframework.web.client.RestClientException: Error while extracting response for type [class java.lang.Boolean] and content type [application/json]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.lang.Boolean` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.Boolean` out of START_OBJECT token
at [Source: (PushbackInputStream); line: 1, column: 1]
I'm not exactly sure why, but the return contentType() in the controller return needed to be removed. For some reason Jackson was not parsing the return type properly. Then either a primitive boolean or the class Boolean works.
Main problem, is that your implementation tries to respond with text/plain without registering any Boolean to text/plain converter.
You have several options to solve that:
Just return (response with) the "default (media) type":
return ResponseEntity
.ok()
.body(rr);
If you need to respond with text/plain, then
a. ResponseEntity<String> would be the straight-forward solution:
#PostMapping(value = "/test2")
public ResponseEntity<String> // String!!! not Boolean ... {
return ResponseEntity
.ok()
.contentType(MediaType.TEXT_PLAIN) // explicit media type here or as #PostMapping.produces attribute
.body(String.valueOf(rr)); // convert the boolean here
}
b. Or to really register a custom(ized) (Boolean<->text/plain) converter ...
Then we could test 1. (with TestRestTemplate) like:
#Test
public void test1() throws URISyntaxException {
final String baseUrl = "http://localhost:" + randomServerPort + "/test/";
URI uri = new URI(baseUrl);
// true:
ResponseEntity<Boolean> result = this.restTemplate.postForEntity(uri + "?update=true", null /*here goes normally the "post body"/entity*/, Boolean.class);
assertThat(result.getStatusCodeValue()).isEqualTo(HttpStatus.OK.value());
assertThat(result.getBody()).isTrue();
}
and 2. accordingly with string result:
#Test
public void test2() throws URISyntaxException {
final String baseUrl = "http://localhost:" + randomServerPort + "/test2/";
URI uri = new URI(baseUrl);
ResponseEntity<String> result = this.restTemplate.postForEntity(uri + "?update=true", null, String.class);
assertThat(result.getStatusCodeValue()).isEqualTo(HttpStatus.OK.value());
assertThat(Boolean.valueOf(result.getBody())).isTrue();
}
Please consider, we have several (out-of-the-box) options regarding "content encoding" and "how to pass this update parameter".
For brevity, simplicity and lack of need I omit any post objects and headers (null, which would go as the second method argument), and passed the only parameter as "URI parameter".
Also consider the note on RestTemplate, which could also be applied to TerstRestTemplate:
NOTE: As of 5.0 this class is in maintenance mode, with only minor requests for changes and bugs to be accepted going forward. Please, consider using the org.springframework.web.reactive.client.WebClient which has a more modern API and supports sync, async, and streaming scenarios.
Controller:
#ApiOperation(value = " update record", response = APIResponse.class)
#ApiResponses(value = {#ApiResponse(code =200, message = "OK"),
#ApiResponses(value = {#ApiResponse(code =500, message = "Internal server error"),
#ApiResponses(value = {#ApiResponse(code =404, message = "NO_RECORD")})
#PutMapping(value = "/update/{id}")
#ResponseBody
public ResponseEntity<APIResponse> updateRecord(HttpServletRequest request, #RequestBody RecordDTO input, #PathVariable(value="id") int code){
APIResponse response = null;
try{
response = service.updateRecord(code, input);
}
catch(JSONException e){
log.error("Error Parsing JSON");
response = new APIResponse(HttpStatus.INTERNAL_SERVER_ERROR, ERROR_JSON_PARSING, ERROR);
}
return new ResponseEntity<>(response, HttpStatus.OK);
}
my test case foor controller:
#Test
public void update() throws Exception{
RecordDTO recordDto = new RecordDTO();
Object mapper = new ObjectMapper();
String value = mapper.writeValueAsString(StationDTO);
given(service.updateRecord(anyInt(), any(RecordDTO.class))).willThrow(JSONException.class);
mockMvc.perform(put(baseUrl + "/update/12")
.contentType(MediaType.APPLICATION_JSON).content(value))
.andExpect(status().isInternalservererror())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status",Matchers.is("INTERNAL_SERVER_ERROR")))
.andExpect(jsonPath("$.message",Matchers.is("ERROR_JSON_PARSING")))
.andExpect(jsonPath("$.resposeStatus",Matchers.is("ERROR")));
APIResponse response = new APIResponse(HttpStatus.OK, SUCCESS, SUCCESS, null);
given(service.updateRecord(anyInt(), any(RecordDTO.class))).willReturn(response);
mockMvc.perform(put(baseUrl + "/update/12")
.contentType(MediaType.APPLICATION_JSON).content(value))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status",Matchers.is("OK")))
.andExpect(jsonPath("$.message",Matchers.is("SUCCESS")))
.andExpect(jsonPath("$.resposeStatus",Matchers.is("SUCCESS")));
}
DTO:
public class RecordDTO{
private String id;
private String name;
private String number;
}
I am getting java.lang assertion error expected 500 but was 200. I don't what is wrong with the test case.. Is there any other way to write the test case? Also can you recommend any platform from where i can gain knowledge of how to write test cases then do comment down. Thanks for the help!
Seems like your mocked service is not injecting into your controller.
Alternative solution (I assume you use Spring-Boot):
DisableAutowireRequireInitializer. This will prevent to load all dependencies inside your Controller.
Create inside your ControllerTest inner class: private static ServiceImplMock entends ServiceImpl
Now, override updateRecord method inside ServiceMock to do your testing cases
#Override
public APIResponse updateRecord(int code, RecordDTO input) throws JSONException {
if(code == 12) throw new JSONException(...)
else your_business_logic
}
Now, add this ServiceImplMock into your #SpringBootTest
#SpringBootTest(classes = {
Controller.class,
ControllerTest.ServiceImplMock.class,
...
})
#AutoConfigureMockMvc
#ContextConfiguration( initializers = {DisableAutowireRequireInitializer.class })
class ControllerTest {
Now, your test cases should work (Remove given(...).willThrow(...); since we don't need it anymore)
Also can you recommend any platform from where i can gain knowledge of how to write test cases then do comment down
https://www.baeldung.com/junit
https://www.baeldung.com/spring-boot-testing
https://mkyong.com/spring-boot/spring-boot-junit-5-mockito/
Say we have an instance of o.s.w.reactive.function.server.ServerResponse.
What is the proper way to fetch the contents of its body, in other words how to implement fetchBodyAsString function?
test(){
ServerResponse response = getResponseFromService("mock data");
String body = fetchBodyAsString(response);
assertEquals("hello", body);
}
Could you also elaborate a bit on why does ServerResponse have methods for everything (cookies(), headers(), statusCode()), but the response body? I guess there should be a way to get the body with writeTo() method, although it is absolutely vague how to use it.
I was digging around for something similar for unit testing purposes, and stitched together the below code. It's in Kotlin, but should be relatively easy to translate to Java and solve your problem (though it definitely does seem a bit hacky).
fun fetchBodyAsString(serverResponse: ServerResponse): String {
val DEFAULT_CONTEXT: ServerResponse.Context = object : ServerResponse.Context {
override fun messageWriters(): List<HttpMessageWriter<*>> {
return HandlerStrategies.withDefaults().messageWriters()
}
override fun viewResolvers(): List<ViewResolver> {
return Collections.emptyList()
}
}
// Only way I could figure out how to get the ServerResponse body was to have it write to an exchange
val request = MockServerHttpRequest.get("http://thisdoenstmatter.com").build()
val exchange = MockServerWebExchange.from(request)
serverResponse.writeTo(exchange, DEFAULT_CONTEXT).block()
val response = exchange.response
return response.bodyAsString.block()!!
}
Basically needed to create a fake MockServerWebExchange and have the ServerResponse write to it to translate it into a MockServerHttpResponse of which you can pull the response body out of fairly painlessly. This is definitely not elegant, but it works.
Also note, I didn't test the above function itself, just that it compiles. It should work though as the function's inner code is exactly what we're using.
As for your other questions about ServerResponse, I don't know the answers, but am curious about that as well!
As far as i know ServerResponse is used at the controller or router function.
For testing you can use WebTestClient
#Autowired
WebTestClient webTestClient;
#Test
void test() {
webTestClient.get()
.exchange()
.expectStatus()
.isOk()
.expectHeader()
.contentType(MediaType.APPLICATION_JSON)
.expectBody()
.jsonPath("data.name").isEqualTo("name");
}
or
#Autowired
WebTestClient webTestClient;
#Test
void test() {
FluxExchangeResult<String> result = webTestClient.get()
.exchange()
.returnResult(String.class);
int rawStatusCode = result.getRawStatusCode();
HttpStatus status = result.getStatus();
HttpHeaders responseHeaders = result.getResponseHeaders();
String stringResponseBody = result.getResponseBody().blockFirst();
}
This is based on Alan Yeung solution above, except in Java. There has to be a better 'native' way to do this without loading application context.
public class ServerResponseExtractor {
public static <T> T serverResponseAsObject(ServerResponse serverResponse,
ObjectMapper mapper, Class<T> type) {
String response = serverResponseAsString(serverResponse);
try {
return mapper.readValue(response, type);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
public static String serverResponseAsString(ServerResponse serverResponse) {
MockServerWebExchange exchange = MockServerWebExchange.from(
MockServerHttpRequest.get("/foo/foo"));
DebugServerContext debugServerContext = new DebugServerContext();
serverResponse.writeTo(exchange, debugServerContext).block();
MockServerHttpResponse response = exchange.getResponse();
return response.getBodyAsString().block();
}
private static class DebugServerContext implements ServerResponse.Context {
#Override
public List<HttpMessageWriter<?>> messageWriters() {
return HandlerStrategies.withDefaults().messageWriters();
}
#Override
public List<ViewResolver> viewResolvers() {
return Collections.emptyList();
}
}
}
Another way to test the body inside a unit test is to cast the ServerResponse to an EntityResponse. This does show a warning for an unchecked cast but inside a controlled unit test I wasn't too worried about it. This just exposes the object that was set using bodyValue() before it is serialized. If you are trying to test the serialization of said body this might not work for your needs.
val entityResponse = serverResponse as EntityResponse<{ Insert Class of Body }>
val bodyObject = entityResponse.entity()
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!
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...