I have the following contract generating script:
VALID_JSON_STRING = '{}'
[
Contract.make {
name("insertSomething_ShouldReturnHttp200")
description("POST should do sth")
request {
method 'POST'
url REQUEST_URL
body(
value: VALID_JSON_STRING
)
headers {
contentType(applicationJson())
}
}
response {
status 200
headers { contentType(applicationJson()) } }
}
]
But it gets compiled to:
#Test
public void insertSomething_ShouldReturnHttp200() throws Exception {
// given:
MockMvcRequestSpecification request = given()
.header("Content-Type", "application/json")
.body("{\"value\":{}}");
// when:
ResponseOptions response = given().spec(request)
.post("...");
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).matches("application/json.*");
}
Notice the .body("{\"value\":{}}"); here.
Instead it should be .body("{\"value\":\"{}\"}");. It should not convert the JSON-string to an actual JSON. How do I prevent this?
EDIT: It was labeled as a bug now: https://github.com/spring-cloud/spring-cloud-contract/issues/652
Related
i have a problem testing an endpoint which use #ModelAttribute I don't know very well how to test with this annotation and the test response is java.lang.AssertionError: Content type not set , here is the controller method:
#PostMapping
public ResponseEntity<?> createTestimonials(#ModelAttribute(name = "testimonialsCreationDto") #Valid TestimonialsCreationDto testimonialsCreationDto) {
try {
return ResponseEntity.status(HttpStatus.CREATED).body(iTestimonials.createTestimonials(testimonialsCreationDto));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(e.getMessage());
}
}
Here is the test:
#Test
void createTestimonials() throws Exception {
//Given
String name = "Testimonio 159";
String contentTestimonial = name + " content!";
TestimonialsCreationDto testimonialsCreationDto = new TestimonialsCreationDto();
testimonialsCreationDto.setName(name);
testimonialsCreationDto.setContent(contentTestimonial);
//When
mockMvc.perform(post("/testimonials")
.flashAttr("testimonialsCreationDto", testimonialsCreationDto)
.contentType(MediaType.MULTIPART_FORM_DATA)
.content(objectMapper.writeValueAsString(testimonialsCreationDto))
.characterEncoding("UTF-8"))
//Then
.andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.name", is(name)))
.andExpect(jsonPath("$.content", is(contentTestimonial)));
verify(testimonialsService).createTestimonials(any());
}
MockHttpServletRequest:
HTTP Method = POST
Request URI = /testimonials
Parameters = {}
Headers = [Content-Type:"multipart/form-data;charset=UTF-8", Content-Length:"74"]
Body = {"name":"Testimonio 159","image":null,"content":"Testimonio 159 content!"}
Session Attrs = {}
MockHttpServletResponse:
Status = 200 ---> IDK why response with 200 code
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Content type not set
You have to add path value to PostMapping :
#PostMapping(path = "/testimonials", produces = MediaType.MULTIPART_FORM_DATA)
I am using jdk 1.8. I have a rest end point in java controller as :
#PostMapping("/filters")
public ResponseEntity<StatsDTO> listWithFilter(
#RequestBody(required = false) String filter
) {
try {
...............
}
}
Test snippet against above controller is passing (getting back expected result in this snippet) as :
#Test
public void findReferralTest15() throws IOException {
String result = webClient.post()
.uri(endpoint(helper.entity + "/filters"))
.contentType(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, clientUser())
.body(BodyInserters.fromObject(buildJsonForQuery15()))
.exchange()
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.expectStatus().isOk()
.expectBody(String.class).returnResult().getResponseBody();
ObjectMapper mapper = new ObjectMapper();
ResponseList referralList = mapper.readValue(result, ResponseList.class);
}
public String buildJsonForQuery15() {
String json = "{\"billType\":{\"INTAKE\":true}}";
return json;
}
Now when I am trying to integrate with front end (Angular 7 on typescript), I have to do JSON.stringify twice (to a json object or filter to be submitted as requestbody) to make it work with the back end. I am getting null otherwise as the value of the "filter" ( in the request body) at the java controller end.
So with double JSON.stringify submitted result from our front end is (WHEN IT WORKS):
"{\"billType\":{\"INTAKE\":true}}"
So with single JSON.stringify submitted result from our from end is (WHEN IT DOESN'T WORK):
{"billType":{"INTAKE":true}}
Question : What should be the data type of requestBody "filter", in the java controller, to make it work with single JSON.stringify?
I tried json.org.JsonObject as datatype for "filter" but it did not make any difference.
Thanks in advance.
Front end snippet:
const fullUrl = `${this.referralsUrl}/filters?first=${first}&max=${max}`;
const headerDict = {
"Content-Type": "application/json; charset=utf-8",
Accept: "application/json",
"Access-Control-Allow-Headers": "Content-Type"
};
const headers = new HttpHeaders(headerDict);
if (filters) {
const requestBody = JSON.stringify(filters);
return this.http
.post<Page<ClinAssistReferral>>(fullUrl, JSON.stringify(requestBody), { headers })
.pipe(
map((data: any) => {
...........
}
}
If the body is not dynamic, making a DTO for the request is preferred.
If body is dynamic, I would suggest you to try JsonNode from jackson. See if this library can
import com.fasterxml.jackson.databind.JsonNode;
...
#PostMapping("/filters")
public ResponseEntity<StatsDTO> listWithFilter(
#RequestBody(required = false) JsonNode filter
) {
try {
var intake = filter.get("billType").get("INTAKE").asBoolean()
...............
}
}
In my ionic 5.0.0 application I'm using cordova's native HTTP to make the rest calls. Below is the code snippet of my logout function.
But when i execute this function i'm getting following error.
"advanced-http: \"data\" argument supports only following data types: String"
logout() {
this.setData("url", "/web/oauth/revoke-token");
let apiUrl = this.getBaseUrl() + this.getData("url");
const headers = {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
'Authorization': 'Basic Y2hyR3liUnBxQUR3X2VDemw5dzc0cHU4dXNnYTpKdmZ1azgyYnBUQlVnNDJ6NU1hZFhXOWJPeElh'
};
const params = {
'companyId': this.getData("COMPANY_ID"),
'token': this.getData("ACCESS_TOKEN"),
'client_id': this.getData("CLIENT_ID"),
'token_type_hint': 'access_token'
};
this.nativeHttp.post(apiUrl, params, headers).then(response => {
console.log("success response: "+response);
})
.catch(error => {
console.log("error response: "+error);
});
console.log("finished");
}
Here is my Spring controller which receives the params.
#RequestMapping(value = "/oauth/revoke-token", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<Object> logout(HttpServletRequest request) {
String clientId = request.getParameter(OAuth2Constants.CLIENT_ID);
String token = request.getParameter(OAuth2Constants.TOKEN);
String tokenTypeHint = request.getParameter(OAuth2Constants.TOKEN_TYPE_HINT);
String companyId = request.getParameter(WebConstants.COMPANY_ID_PARAMETER);
}
But unfortunately all params receives in the controller as null.
Can someone help me?
Finally I found a solution for the issue. Simply set the data serializer for http request as follows before doing the post call.
this.nativeHttp.setDataSerializer( "urlencoded" );
I have one GET Rest-endpoint in my sample app which returns some data based on criteria it can also return null if no data is available with HTTP status as 204 else 200 OK if data is available.
#GetMapping("/homepage")
public ResponseEntity getHomePageCollections(#RequestHeader(value = HEADER_APP_TOKEN) String headerAppToken) {
CollectionObject homepageCollections = null;
String errorMessage = null;
HttpStatus httpStatus;
try {
homepageCollections = collectionService.getHomePageCollections();
if (nonNull(homepageCollections)) {
httpStatus = HttpStatus.OK;
LOGGER.info("{} Response Status from CollectionController -- getHomePageCollections !! {}", TRANSACTION_SUCCESS_CODE, TRANSACTION_SUCCESS);
} else {
httpStatus = HttpStatus.NO_CONTENT;
LOGGER.info("{} Response Status from CollectionController -- getHomePageCollections !! {}", NO_CONTENT_CODE, NO_CONTENT);
}
} // catch logic
return ResponseEntity.status(httpStatus).contentType(MediaType.APPLICATION_JSON).body(httpStatus == HttpStatus.OK || httpStatus == HttpStatus.NO_CONTENT ? homepageCollections : errorMessage);
}
I have 2 questions, first is how to assert the content type is
set by the controller in my unit test
Unit Test
#Test
public void testGetHomePageCollection() {
when(collectionService.getHomePageCollections()).thenReturn(null);
ResponseEntity responseEntity = collectionController.getHomePageCollections(HEADER_APP_TOKEN);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
}
When the homepageCollections is null spring auto-sets the content type as octet-stream, is there a reason behind it?
The ContentType is present in the headers of the response, so you can test the same by accessing it as below:
#Test
public void testGetHomePageCollection() {
when(collectionService.getHomePageCollections()).thenReturn(null);
ResponseEntity responseEntity = collectionController.getHomePageCollections(HEADER_APP_TOKEN);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
assertThat(responseEntity.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
}
I am trying to get simple string from request body but keep getting errors
Handler:
#RestController
public class GreetingHandler {
public Mono<ServerResponse> hello(ServerRequest request) {
String contentType = request.headers().contentType().get().toString();
String body = request.bodyToMono(String.class).toString();
return ServerResponse.ok().body(Mono.just("test"), String.class);
}
}
Router:
#Configuration
public class GreetingRouter {
#Bean
public RouterFunction<ServerResponse> route(GreetingHandler greetingHandler) {
return RouterFunctions
.route(RequestPredicates.POST("/hello"),greetingHandler::hello);
}
}
Request works i can see the contenType (plainTexT) and i get the response in postman but no way i cant get to request body. The most common error i get is MonoOnErrorResume. How do i convert the body from request into String?
You will have to block to get to the actual body string:
String body = request.bodyToMono(String.class).block();
toString() will just give you the string representation of your Mono object.
Here is what block does:
https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html#block--
Update:
I wasn't aware that blocking on the http thread is not possible (anymore?).
Here is an adapted version of your hello controller method that prints "Hello yourInput" on the console and also returns that string in the response.
public Mono<ServerResponse> hello(ServerRequest request) {
Mono<String> requestMono = request.bodyToMono(String.class);
Mono<String> mapped = requestMono.map(name -> "Hello " + name)
.doOnSuccess(s -> System.out.println(s));
return ServerResponse.ok().body(mapped, String.class);
}
Can you use #RequestBody annotation?
public Mono<ServerResponse> hello(#RequestBody String body, ServerRequest request) {
String contentType = request.headers().contentType().get().toString();
return ServerResponse.ok().body(Mono.just("test"), String.class);
}