How to test the error output of a Spring Boot Controller? - json

I have implemented a simple controller with a simple request / response.
Controller
#RestController
#RequestMapping("/")
public class HelloWorldController extends AbstractRestController {
#RequestMapping(value = "/hello", method = RequestMethod.POST)
public HelloWorldResponse sayHello(#Valid #RequestBody HelloWorldRequest request) {
String message = String.format("Hello %s!", request.getSayHelloTo());
return new HelloWorldResponse(message);
}
}
Request
public class HelloWorldRequest {
#NotEmpty
#NotNull
private String sayHelloTo;
protected HelloWorldRequest() {
}
public HelloWorldRequest(String sayHelloTo) {
this.sayHelloTo = sayHelloTo;
}
public String getSayHelloTo() {
return sayHelloTo;
}
#Override
public String toString() {
return "HelloWorldRequest{" +
"sayHelloTo='" + sayHelloTo + '\'' +
'}';
}
}
When i want to test the correct output for the default error handling i seem to be unable to check the output of the default json format using a unit test. The response always seems to be empty. When i fire the request via a normal curl command is see a correct response. I assume this is because the returned JSON cannot be mapped on the HelloWorldResponse. Is there any way of checking if the returned output is valid on the response body?
Test
class TestSpec extends Specification {
MockMvc mockMvc
def setup() {
mockMvc = MockMvcBuilders.standaloneSetup(new HelloWorldController()).build()
}
def "Test simple action"() {
when:
def response = mockMvc.perform(post("/hello")
.contentType(MediaType.APPLICATION_JSON)
.content('{"sayHelloTo": ""}')
)
then:
response.andExpect(status().isOk())
}
}
Json Response
{
"timestamp" : 1426615606,
"exception" : "org.springframework.web.bind.MissingServletRequestParameterException",
"status" : 400,
"error" : "Bad Request",
"path" : "/welcome",
"message" : "Required String parameter 'name' is not present"
}

try to debug with print() method, may be exception is thrown during execution.
MvcResult andReturn = mockMvc.perform(get("/api")
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andReturn();

The #Controller class you posted and the Test case (and response) don't seem to match (you're calling /hello in your test case and your #RequestMapping says /welcome ... Can we see the correct code? It looks like you have a #RequestParam("name") set, but you're not passing it.

Related

How to handle two data types coming in same response for same parameter name

I am calling a third-party API that returns two different values for the same parameter name as below,
ActivationResponse activationResponse = new ActivationResponse();
ResponseEntity<ActivationResponse> response = null;
response = restTemplate.exchange(Url, HttpMethod.POST, request, ActivationResponse.class);
activationResponse = response.getBody();
Error response:
{
"executionCode":"2",
"executionMessage":"NullPointerException Occured!",
"response":"java.lang.NullPointerException"
}
Success response:
{
"executionCode" : "0",
"executionMessage" : "SUCCESS",
"response" : {
"orderID" : "79966036"
}
}
As the sample response response parameter can come as a string or with a JSON object.
Currently the response model is as below,
public class ActivationResponse {
private String executionCode;
private String executionMessage;
private ActivationResponseDetails response;
}
public class ActivationResponseDetails {
private String orderID;
}
When the error comes, an exception is thrown indicating that it can't handle the response parameter. Please advice how to handle both success and failure scenarios without issues.
Please Note that approach in this answer is not possible, because I have to print the logs in following way, so using #JsonIgnore will not show that parameter on the log.
Logs are printed like this,
log.info("ActivationResponse json : {}", mapper.writerWithDefaultPrettyPrinter().writeValueAsString(response.getBody()));
If you insist on having a one-size-fits-all Response object, setting the type of the response field to Object, may do the trick:
public class ActivationResponse {
private String executionCode;
private String executionMessage;
private Object response;
}
The field 'response' should now resolve to a simple String (success) or a more involved Java object (error).

How to pass two arguments of custom type in spring controller using request body and method PUT

Here users pass to the method normally:
#PutMapping
#RequestMapping("/update_user")
public String update(#RequestBody List<CustomUser>users) {
return ......
}
Postman PUT request body:
[
{"name":"Name1"},
{"name":"Name2"}
]
But here I receive an error: "Failed to resolve argument 1 of type CustomUser":
#PutMapping
#RequestMapping("/update_user")
public String update(#RequestBody CustomUser user1, CustomUser user2) {
return ......
}
Postman PUT request body:
{
"user1":{"name":"Name1"},
"user2":{"name":"Name2"}
}
What am I doing wrong?
RequestBody annotated parameter is expected to hold the entire body of the request and bind to one object, so you essentially will have to go with one object only
Now, Either you can wrap them both like this
public class Payload {
CustomUser user1;
CustomUser user2;
//getters & setters
}
and use that for RequestBody
#PostMapping
#RequestMapping("/update_user")
public String update(#RequestBody Payload users) {
return ......
}
Or you can use a Map<String, CustomUser> for RequestBody
#PostMapping
#RequestMapping("/update_user")
public String update(#RequestBody Map<String, CustomUser> users) {
//you can access like this
CustomUser user1 = users.get("user1");
CustomUser user2 = users.get("user2");
}
Another thing to note, you are mapping as POST request but your comment says "PUT". check that as well.

How to marshal to JSON/XML when an exception occurs with Camel rest-dsl

I have a REST-dsl camel route with binding: json_xml
with .type() and outType(). It works perfectly when no exception occurs. That is json input gives json output. Xml input gives xml output.
However, when I get an IllegalArgumentException I always return XML. I create a ErrorResponse POJO when the exception occurs. The CONTENT_TYPE is set to "application/json" for json. How do I return a POJO and let camel marhal to JSON/XML when an Exception occurs(given ResBindingMode.json_xml)?
onException(IllegalArgumentException.class)
.log(LoggingLevel.ERROR, LOGGER, "error")
.handled(true)
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(400))
.setHeader(Exchange.CONTENT_TYPE, exchangeProperty(Exchange.CONTENT_TYPE))
.bean(errorResponseTranslator);
restConfiguration().component("restlet").port(port).skipBindingOnErrorCode(true)
.bindingMode(RestBindingMode.json_xml);
rest("/whatever/api/v1/request")
.post().type(RequestDto.class).outType(ResponseDto.class)
.route()
.setProperty(Exchange.CONTENT_TYPE, header(Exchange.CONTENT_TYPE))
...process
ErrorDto:
#XmlRootElement(name = "errorResponse")
#XmlAccessorType(XmlAccessType.PROPERTY)
public class ErrorResponseDto {
private String errorCode;
private String message;
#XmlElement(name = "message")
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
#XmlElement(name = "errorCode")
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
}
You need to set the content type explicit to XML then
.setHeader(Exchange.CONTENT_TYPE, exchangeProperty(Exchange.CONTENT_TYPE))
Should be
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
The error occurs because the outType not dynamic. It seems like its a camel bug. That is: the outType must be an XMLROOT that contains OK and ERROR dto. It is possible to quickfix this if you use a XMLroot that takes an any element with lax=true(here you can add the ErrorDto or okResponseDto). But it does add an unwanted element. For now we have to implement a custom contentNegotiator.
This is when using skipBindingOnError is set to false.

unit test spring controller with WebTestClient and ControllerAdvice

I'm trying to unit test my controller and the specific case which is : my service return a Mono.Empty, I throw a NotFoundException and I wan't to make sure I'm getting a 404 exception
here's my controller :
#GetMapping(path = "/{id}")
public Mono<MyObject<JsonNode>> getFragmentById(#PathVariable(value = "id") String id) throws NotFoundException {
return this.myService.getObject(id, JsonNode.class).switchIfEmpty(Mono.error(new NotFoundException()));
}
Here's my controller advice :
#ControllerAdvice
public class RestResponseEntityExceptionHandler {
#ExceptionHandler(value = { NotFoundException.class })
protected ResponseEntity<String> handleNotFound(SaveActionException ex, WebRequest request) {
String bodyOfResponse = "This should be application specific";
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Resource not found");
}
}
and my test :
#Before
public void setup() {
client = WebTestClient.bindToController(new MyController()).controllerAdvice(new RestResponseEntityExceptionHandler()).build();
}
#Test
public void assert_404() throws Exception {
when(myService.getobject("id", JsonNode.class)).thenReturn(Mono.empty());
WebTestClient.ResponseSpec response = client.get().uri("/api/object/id").exchange();
response.expectStatus().isEqualTo(404);
}
I'm getting a NotFoundException But a 500 error not a 404 which mean my advice hasn't been called
stack trace :
java.lang.AssertionError: Status expected:<404> but was:<500>
> GET /api/fragments/idFragment
> WebTestClient-Request-Id: [1]
No content
< 500 Internal Server Error
< Content-Type: [application/json;charset=UTF-8]
Content not available yet
any idea ?
I believe you can delete this controller advice and just have the following:
#GetMapping(path = "/{id}")
public Mono<MyObject<JsonNode>> getFragmentById(#PathVariable(value = "id") String id) {
return this.myService.getObject(id, JsonNode.class)
.switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)));
}
As for ResponseEntityExceptionHandler, this class is part of Spring MVC so I don't think you should use it in a WebFlux application.

How to get response in JSON format using #ExceptionHandler in Spring MVC

I am new to this #ExceptionHandler. I need to return response in JSON format if there is any exception. My code is returning response in JSON format if the operation is successful. But when any exception is thrown it is return HTML response as I have used #ExceptionHandler.
Value and reason in #ResponseStatus is coming properly but in HTML. How can I can change it to a JSON response? Please help.
In my controller class i have this methods:
#RequestMapping(value = "/savePoints", method = RequestMethod.POST, consumes = "application/json", produces = "application/json;charset=UTF-8")
public #ResponseBody
GenericResponseVO<TestResponseVO> saveScore(
#RequestBody(required = true) GenericRequestVO<TestVO> testVO) {
UserContext userCtx = new UserContext();
userCtx.setAppId("appId");
return gameHandler.handle(userCtx, testVO);
}
Exception handling method:
#ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Error in the process")
#ExceptionHandler(Exception.class)
public void handleAllOtherException() {
}
You can annotate the handler method with #ResponseBody and return any object you want and it should be serialized to JSON (depending on your configuration of course). For instance:
public class Error {
private String message;
// Constructors, getters, setters, other properties ...
}
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(MethodArgumentNotValidException.class)
public Error handleValidationException(MethodArgumentNotValidException e) {
// Optionally do additional things with the exception, for example map
// individual field errors (from e.getBindingResult()) to the Error object
return new Error("Invalid data");
}
which should produce response with HTTP 400 code and following body:
{
"message": "Invalid data"
}
Also see Spring JavaDoc for #ExceptionHandler which lists possible return types, one of which is:
#ResponseBody annotated methods (Servlet-only) to set the response content. The return value will be converted to the response stream using message converters.
Replace
#ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Error in the process")
by
#ResponseStatus(value = HttpStatus.NOT_FOUND)
the 'reason' attribute force html render!
I've waste 1 day on that.....