Spring boot restful service Post request always returns null as response message in test code using Junit - rest

This is a demo controller.
#PostMapping("/rest/new")
public ResponseEntity<MessageDto> newUser(#RequestBody UserDto userDto) {
return ResponseEntity.ok(new MessageDto().setMessage(userService.createUser(userDto)));
}
This is service layer.
#Override
public String createUser(UserDto userDto) {
// Do Something
return "Successful!!";
}
This is the test code to test the controller
#Test
public void testPostRestController() throws Exception {
UserDto userDto = new UserDto();
userDto.setName("AA");
userDto.setEmail("a#a.a");
userDto.setId((long) 1);
when(userService.createUser(userDto)).thenReturn("Successful!!");
mockMvc.perform(post("/rest/new")
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(new ObjectMapper().writeValueAsString(userDto)))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(jsonPath("$.message", is(userService.createUser(userDto))))
.andDo(MockMvcResultHandlers.print());
}
The problem is when I run the test code, it is supposed to check the response status and response message. The response status matched but the problem is the response message always returns as null.
error:
java.lang.AssertionError: JSON path "$.message"
Expected: is "Successful!!"
but: was null
Am I missing something here?

Try using
when(userService.createUser(any(UserDto.class))).thenReturn("Successful!!");
Instead of
when(userService.createUser(userDto)).thenReturn("Successful!!");
This should ideally fix the NPE

Related

SpringBoot Test Cases not working in the way they were expected to

I have been working on a basic Spring Boot Application building REST APIs. I have learnt to write the APIs and now I am trying to write Unit Tests for them. I have written one unit test for the get API and one for the post API. The post API test seems to be running fine but the get api test fails. I am not sure why. I am not sure if the get test is running before the post and hence nothing is available so it fails?
I have tried changing the order in which the tests are written in order to see the execution order changes but it hasn't changed.
#RunWith(SpringRunner.class)
#WebMvcTest(value = ProjectRestController.class)
public class ProjectControllerTest
{
private String baseURL = "http://localhost:8080/";
private String expectedResult = "{id:0, name:\"Testing Course 0\", description: \"This is a test for course 0\"}";
#Autowired
private MockMvc mockMvc;
#MockBean
private ProjectService projectService;
Project mockProject = new Project(0, "Testing Course 0", "This is a test for course 0");
#Test
public void addProject() throws Exception
{
mockMvc.perform(MockMvcRequestBuilders.post(baseURL+"/projects")
.content(asJsonString(new Project(0, "Test 0", "Testing Project 0")))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
//This test does not seem to work. Returns 404
#Test
public void getProject() throws Exception
{
mockMvc.perform(MockMvcRequestBuilders.get(baseURL+"/projects/0")
.accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk());
}
public static String asJsonString(final Object obj)
{
try
{
return new ObjectMapper().writeValueAsString(obj);
} catch (Exception e)
{
throw new RuntimeException(e);
}
}
}
I expected a status 200 from the GET as the post is working fine however the get returns a 404.
The issue is due to you have a base URL you should not send the base URL, pass only /project/0

How to resend HttpStatus from used REST service from another REST service

Problem:
My REST application is using another REST application as it is a "hub" for several other applications.
When a request to this "hub" creates a HttpClientErrorException on one of the other REST applications it is managing, I want that StatusCode (with message) to be presented to the user of my "hub" and not just the standard internal server 500 error...
How can this be done?
I am using spring-boot 2.1.4 for my "hub" REST application which is sending requests to other REST applications...
Simple failing test spring-boot test case:
#Test
#SuppressWarnings("unchecked")
#DisplayName("should use original rest exception")
void shouldUseRestException() {
when(restTemplateMock.exchange(anyString(), any(), any(), any(ParameterizedTypeReference.class)))
.thenThrow(new HttpClientErrorException(HttpStatus.I_AM_A_TEAPOT, "holy crap!"));
assertThatExceptionOfType(RestClientException.class).isThrownBy(() -> testRestTemplate.exchange(
url("/1001"), HttpMethod.GET, null, listOfMyGenericTypes()
)).withMessage("Http client says: holy crap!");
}
My RestControllerAdvice:
#RestControllerAdvice
public class HubControllerAdvice {
#ResponseBody
#ExceptionHandler
public ResponseEntity<Object> handleHttClientErrorException(HttpClientErrorException httpClientErrorException) {
throw new HttpClientErrorException(httpClientErrorException.getStatusCode(), "Http client says: " + httpClientErrorException.getMessage());
}
}
JUnit failure:
org.opentest4j.AssertionFailedError:
Expecting message:
<"Http client says: holy crap!">
but was:
<"Error while extracting response for type [java.util.List<MyGenericType>] and content type [application/json;charset=UTF-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.util.ArrayList` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList` out of START_OBJECT token
at [Source: (PushbackInputStream); line: 1, column: 1]">
I have a working solution, but this will change the behaviour of the service and I would really want to avoid that. This is to actually return the response entity with status code of the exception as a header in my api... although this works (see below), it requires that users of my hub change their expectations of how it works:
My RestControllerAdvice:
#RestControllerAdvice
public class BidragDokumentRestControllerAdvice {
#ResponseBody
#ExceptionHandler
public ResponseEntity<Object> handleHttClientErrorException(HttpClientErrorException httpClientErrorException) {
return ResponseEntity
.status(httpClientErrorException.getStatusCode())
.header(HttpHeaders.WARNING, "Http client says: " + httpClientErrorException.getMessage())
.build();
}
}
My now working JUnit test:
#Test
#SuppressWarnings("unchecked")
#DisplayName("should return empty response with original exception warning")
void shouldReturnEmptyResponseWithOriginalExceptionWarning() {
when(restTemplateMock.exchange(anyString(), any(), any(), any(ParameterizedTypeReference.class)))
.thenThrow(new HttpClientErrorException(HttpStatus.I_AM_A_TEAPOT, "holy crap!"));
var errorResponser = testRestTemplate.exchange(
url("/1001"), HttpMethod.GET, null, listOfMyGenericTypes()
);
assertThat(Optional.of(errorResponse)).hasValueSatisfying(responseEntity -> assertAll(
() -> assertThat(responseEntity.getStatusCode()).as("status").isEqualTo(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED),
() -> assertThat(responseEntity.getBody()).as("body").isNull(),
() -> {
HttpHeaders httpHeaders = errorResponse.getHeaders();
assertAll(
() -> assertThat(httpHeaders.get(HttpHeaders.WARNING)).as("warning header").isNotNull(),
() -> assertThat(httpHeaders.get(HttpHeaders.WARNING)).as("header value").isEqualTo(List.of("Http client says: 509 holy crap!"))
);
}
));
}

Customising Spring Boot Exception Handling to Prevent Stacktraces Being Returned in Rest Response

How do I configure my spring boot service so that errors such as 500 don't potentially leak implementation details such as stacktraces.
{
"timestamp": "2019/05/01 15:06:17",
"status": 500,
"error": "Internal Server Error",
"message": "Type definition error: [simple type, class net.i2p.crypto.eddsa.math.ed25519.Ed25519LittleEndianEncoding]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class net.i2p.crypto.eddsa.math.ed25519.Ed25519LittleEndianEncoding and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.Collections$UnmodifiableRandomAccessList[0]->........)",
"path": "/api/test"
}
Note: here the stacktrace is in the message and not the exception part of the json.
As you can see I am already formatting the timestamp with:
#Component
public class CustomErrorAttributes extends DefaultErrorAttributes {
private static final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
private static final String TIMESTAMP = "timestamp";
#Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
//Let Spring handle the error first
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
//Format & update timestamp
Object timestamp = errorAttributes.get(TIMESTAMP);
if(timestamp == null) {
errorAttributes.put(TIMESTAMP, dateFormat.format(new Date()));
} else {
errorAttributes.put(TIMESTAMP, dateFormat.format((Date)timestamp));
}
return errorAttributes;
}
}
But I need to handle the message too.
If this 500 was the only error I could just do:
errorAttributes.put("message", "Server error. Contact support.");
However, all the errors go through here and that would override all the messages.
I could check if the status is 500 and only modify it then. However, there are other errors that can be generated that also might leak stacktraces.
Using #RestControllerAdvice seems to require knowing every exception that is generated and having an #ExceptionHandler for each and knowing which status code to respond with.
Is there a cleaner way to handle this?
It may not be the "cleanest" approach, but with projects I've been on we had a "standard format" for our Error Responses across projects, so we had a custom object with the fields that matched our orgs standard (HttpStatus, Reason, ect.) that extended RuntimeException. Then in our controllers, services, repos, ect we would catch exceptions and create this object accordingly and throw the custom one up instead. Based upon where it happened in the app (repo, service, controller, ect.) we could give our own custom verbage to it, but still log out the full exception in our server logs so we could investigate later
For example if we caught an error in our repository we would create our custom error object, set the Reason to DB unavailable (really all the consumer needs to know), set the status to HttpStatus.SERVICE_UNAVAILABLE (we tracked these with reasons and httpstatus with enums to keep status the same across modules), and throw the custom object up to the controller to be returned.
Sorry if this was a longwinded answer that may not give you what you want, I'm not too familiar with how you're trying to do it so figured I'd just give an example of other methods. I'll put some sample code as well
Custom Exception:
data class MyException(
val reason: String,
val httpStatus: HttpStatus? = null
) : RuntimeException(reason)
Method for creation:
fun createApiException(errorCode: ErrorCodeEnum) = MyException(
reason = errorCode.reason,
httpStatus = errorCode.httpStatus,
)
Spring-boot provides us with a standard method to handle exceptions using spring aop concept. You can use the #ControllerAdvice and #Exceptionhandled annotations to handle exceptions from a spring-boot rest endpoint so that a custom exception is always thrown from a rest endpoint with proper error code and error response.
The #ResponseStatus() annotation can be used to customize the response code being thrown.
For example consider the custom exception :
#ResponseStatus(HttpStatus.NOT_FOUND)
public class DataNotFoundException extends RuntimeException {
public DataNotFoundException(String exception) {
super(exception);
}
}
We can throw this error from a rest GET mapping when a data is not found like :
#GetMapping("/trains/{id}")
public Resource<Student> retrieveTrains(#PathVariable long id) {
Optional<Trains> trains = trainRepository.findById(id);
if (!train.isPresent())
throw new DataNotFoundException("id-" + id);
Resource<Trains> resource = new Resource<Trains>(train.get());
ControllerLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllTrains());
resource.add(linkTo.withRel("all-trains"));
return resource;
}
Default error response provided by Spring Boot contains all the details that are typically needed.
However, you might want to create a framework independent response structure for your organization. In that case, you can define a specific error response structure.
For example :
public class ErrorDetails {
private Date timestamp;
private String message;
private String details;
public ErrorDetails(Date timestamp, String message, String details) {
super();
this.timestamp = timestamp;
this.message = message;
this.details = details;
}
To use this error node we use :
#ControllerAdvice
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(DataNotFoundException.class)
public final ResponseEntity<ErrorDetails> handleUserNotFoundException(DataNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(),
request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
#ExceptionHandler(DataNotFoundException.class) indicates that this
method would handle exceptions of the specific type.
new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND) - Create an
error response object and return it with a specific Http Status.
For a more generalized exception handler you can define a method that handles exception of the type Exception.class, that way you don't have to know every exception.
Like :
#ExceptionHandler(Exception.class)
public final ResponseEntity<ErrorDetails> handleAllExceptions(Exception ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(),
request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
}
Reference from : https://www.javaguides.net/2019/02/spring-boot-2-angular-7-crud-example-tutorial.html

Entity of Response is null

currently I am coding on a mock for a rest service we're using. For one case I want to return a 404 with a specific message in the body:
#POST
#Produces(MediaType.APPLICATION_XML)
#Consumes(MediaType.APPLICATION_XML)
#Path("/bookings")
public javax.ws.rs.core.Response performBooking(final BookingRequest booking) {
if (shouldfail(booking)) {
return Response.status(Response.Status.NOT_FOUND).entity("specific message in entity").build();
}
// some more other cases below...
}
If I test the mock with a unit test everything works fine:
final String failedMessage = response.getEntity().toString();
But if I deploy the rest service and call it, I will get the correct 404 code, but the entity is null.
For valid answers I put a BookingResponse object in the entity (simple DTO with some IDs in it) and it works for that. Just the string seems to disappear.
Any idea why my string disappears?

jersey 2.0 jaxrs RI - return json string on exception

I am creating a REST service using jersey 2.0. I am extending WebApplicationException
Method raising a particular exception
if(json.equals("") || json.equals(" ")) {
throw new ArgumentException("bad post data");
}
public class ArgumentException extends RestException {
.....
public ArgumentException(String message) {
super(Status.BAD_REQUEST,message);
}
}
public class RestException extends WebApplicationException {
...........
public RestException(Status status, String message) {
super(Response.status(status)
.entity(message)
.type("text/plain")
.build());
/*
super(Response.status(status)
.entity(new ErrorBean(status.getStatusCode(),message))
.type(MediaType.APPLICATION_JSON)
.build()); */
}
ErrorBean is a POJO
The method that returns error as plain string inside RestException works (right http code 400 and message). However when I try to pass the ErrorBean POJO and use MediaType.APPLICATION_JSON in response I get an error saying "Headers have already been sent" with http error code 500 (so some internal problem with plumbing) and empty response.
I have also looked at this question Returning JSON or XML for Exceptions in Jersey
How can I return the exception with code and message as a JSON like
{"code" : 400, "message" : .... }
Update
I have received answer on SO as well as jersey users mailing list. steps are
A non AJXB POJO does not need any annotations
Register JacksonFeature in your application
ResourceConfig rc = new ResourceConfig().packages("test").register(JacksonFeature.class);
You need to register JacksonFeature in your Application/ResourceConfig, i.e.:
// Create JAX-RS application.
final Application application = new ResourceConfig()
.packages("org.glassfish.jersey.examples.jackson")
.register(JacksonFeature.class)
// No need to register this provider if no special configuration is required.
.register(MyObjectMapperProvider.class);
Take a look at the documentation for Jackson support in Jersey and also at the example.