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()
Related
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/
I have an issue when trying to test a class that represents a Rest Client. I'm using RestTemplate in Spring Boot.
This is the abstract RestClient class:
public abstract class RestClient {
...
public RestResponse sendPostRequest(URI baseUri, String resource, IRestRequest restRequest, ClassresponseClass)
throws ServerException, ClientException {
...
try {
RestTemplate restTemplate = new RestTemplate();
response = restTemplate.exchange(baseUri, HttpMethod.POST, getEntity(restRequest), responseClass);
result = response.getBody();
getLogger().debug("[{}] received", result);
return result;
} catch (HttpClientErrorException e) {
throw new ClientException(e.getCause());
} catch (HttpServerErrorException e) {
throw new ServerException(e.getCause());
} catch (Exception e) {
getLogger().error("Error with cause: {}.", e.getMessage());
}
...
}
}
This is the actual implementation:
public class ActualRestClient extends RestClient {
public RestResponse sendFetchFileRequest(URI baseUri, FetchFileRequest request) throws ServerException, ClientException {
return sendPostRequest(baseUri, "FETCH_FILE", request, RestResponse.class);
}
}
An this is the test:
#RunWith(PowerMockRunner.class)
#PrepareForTest({ActualRestClient.class, RestClient.class})
public class ActualResRestClientTest {
private static final String REQUEST_URI = "something";
#InjectMocks
public ActualRestClient testee;
#Mock
private RestTemplate restTemplate;
#Test(expected = ServerException.class)
public void sendPostRequestWithResponseBody_throwsServerException() throws Exception {
HttpServerErrorException httpServerErrorException = new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR);
when(restTemplate.exchange(Mockito.any(URI.class), eq(HttpMethod.POST), Mockito.any(), eq(FetchFileRequest.class))).thenThrow(httpServerErrorException);
testee.sendFetchFileRequest(new URI(REQUEST_URI), new FetchFileRequest());
}
}
ClientException and ServerException are exceptions created by me by extending Exception class.
My problem is that in the RestClient class another Exception is catched(message:"URI is not absolute") instead of HttpServerErrorException and I can't understand why. Thank you!
As the commenter already expressed: doing new URI("something") already throws at you. But even when you pass a "valid" URI, your code will not work, as there is a misconception on your end. You see:
RestTemplate restTemplate = new RestTemplate();
response = restTemplate.exchange(baseUri, HttpMethod.POST, getEntity(restRequest), responseClass);
That code lives within a method of your class under test. But #InjectMocks works only for fields of classes.
In other words: when your production code gets executed, a new (completely different** ResponseTemplate instance is created. And therefore your mocking spec is irrelevant, because the method isn't invoked on your mock in the first place.
Two choices:
turn that local variable into a field of your class under test (then injecting should work)
or, as you are already using PowerMock(ito), you could use that mocking framework to intercept that call to new().
I suggest you rather use option one, and avoid to use the PowerMock(ito) extension altogether!
I'm trying for more than an hour to test this class. It went so ugly of stubbing the whole components of the method etc. I'd love some advice how to make a better test or refactor the class to make it way easier to test. I could not figure out a way yet.
Class to Test
#Slf4j
public final class HistoryRestService {
static RestTemplate restTemplate = new RestTemplate();
public static Optional<List<History>> findLatestHistories() {
String url = buildUrl();
ResponseEntity<History[]> responseEntity = null;
try {
responseEntity = restTemplate.getForEntity(url, History[].class);
} catch (ResourceAccessException e) {
log.warn("No connection to History persistence. Please check if the history persistence started up properly");
return Optional.empty();
}
History[] histories = responseEntity.getBody();
return Optional.of(Arrays.asList(histories));
}
private static String buildUrl() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("http://");
stringBuilder.append("localhost");
stringBuilder.append(":8081");
stringBuilder.append("/history/get");
return stringBuilder.toString();
}
// For Testing
static void setRestTemplate(RestTemplate restTemplate) {
HistoryRestService.restTemplate = restTemplate;
}
}
Spock Test which fails
class HistoryRestServiceTest extends Specification {
def "test findLatestHistories"() {
given:
History mockedHistory = Mock()
HistoryRestService uut = new HistoryRestService()
History[] expected = [mockedHistory]
RestTemplate mockedRestTemplate = Stub()
ResponseEntity<History> mockedResponseEntity = Stub()
mockedResponseEntity.getBody() >> expected
mockedRestTemplate.getForEntity(_) >> mockedResponseEntity
uut.setRestTemplate(mockedRestTemplate)
when:
def actual = uut.findLatestHistories()
then:
actual.get() == expected
}
}
I'd suggest using real depedency-injection (spring/guice/cdi) instead of static variables.
Furthermore, you should think about what you want to test, is it the correct request and parsing of the network call, then write an integration test using something like mockserver or wiremock to have the whole stack. Or, if you are just concerned with the result handling, then you could move the code that interacts with RestTemplate into a separate method and use partial mocking to mock this method. I'd suggest to use the real integration test, but for the sake of an example this should work, but I didn't verify the code.
#Slf4j
public class HistoryRestService {
private final RestTemplate restTemplate;
public HistoryRestService() {
restTemplate = new RestTemplate();
}
public HistoryRestService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public Optional<List<History>> findLatestHistories() {
try {
return Optional.of(Arrays.asList(getLatestHistories(buildUrl())));
} catch (ResourceAccessException e) {
log.warn("No connection to History persistence. Please check if the history persistence started up properly");
return Optional.empty();
}
}
History[] getLatestHistories(String url) throws {
ResponseEntity<History[]> responseEntity = null;
responseEntity = restTemplate.getForEntity(url, History[].class);
return responseEntity.getBody()
}
private String buildUrl() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("http://");
stringBuilder.append("localhost");
stringBuilder.append(":8081");
stringBuilder.append("/history/get");
return stringBuilder.toString();
}
}
class HistoryRestServiceTest extends Specification {
#Subject
HistoryRestService uut = Spy()
def "test findLatestHistories"() {
given:
History[] expected = [mockedHistory]
when:
def actual = uut.findLatestHistories()
then:
actual.get() == expected
1 * uut.getLatestHistories(_ as String) >> expected
}
def "test findLatestHistories returns empty on exceptions"() {
given:
History[] expected = [mockedHistory]
when:
def actual = uut.findLatestHistories()
then:
!actual.present
1 * uut.getLatestHistories(_ as String) >> {throw new ResourceAccessException()}
}
}
So I have a Spring RestController and one of my endpoints is used to perform operations on a generic typed object passed into my RequestBody as so:
#PostMapping("/endpoint")
public <T extends Comparable<T>> ResponseEntity<Integer> balancingPost(#RequestBody MyCustomObject<T> mco)
So after a lot of searching it doesn't seem this can be done without explicitly stating the type at some point. However as it stands my controller has no way of knowing the type (the program calling the POST does though). So how should I handle this? Is there a way to post my Class of T as well and somehow map it?
Try following
public ResponseEntity<?> balancingPost(#RequestBody MyCustomObject<T> mco) {
ResponseEntity<?> response = null;
try {
/*Some condition*/
if (!auth.equals(authCode)) {
response = new ResponseEntity<>("Unauthorized", HttpStatus.UNAUTHORIZED);
} else {
MyModel model = service.getModel();
response = new ResponseEntity<>(model, HttpStatus.OK);
}
} catch (Exception ex) {
response = new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
ex.printStackTrace();
}
return response;
}
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.