POST: Exception Status expected:<201> but was:<200> - java

Technically it should be a simple task, but I can't find the error.
I want to write a normal "POST method", but when I tested it, it came to a problem: enter code here Status expected:<201> but what:<200>.
My question is, why do I get an OK and not a CREATED?
CODE:
PostMapping in Controller
#PostMapping
public Optional<ADto> createA(#RequestBody ADto a){
return Optional.ofNullable(a);
}
Unit Test
#Test
void verifyPostA() throws Exception {
var a = new ADto(1L, "a");
var aText = objectMapper.writeValueAsString(a);
mockMvc.perform(
MockMvcRequestBuilders.post("/as")
.content(aText)
.contentType(MediaType.APPLICATION_JSON)
)
.andDo(print())
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value("1"));
}

Because for a controller method that executes successfully and does not return ResponseEntity , the default response code is 200.
To configure the response code for such case , you can simply annotate #ResponseStatus on that controller method :
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public Optional<ADto> createA(#RequestBody ADto a){
return Optional.ofNullable(a);
}

Related

Test ExceptionHandler with RestTemplate

I have a method that makes a hit to external API and I have the exception handler is written to handle the errors and send the client-friendly response in case of errors. I have a requirement to test the non 200 OK responses from that external API such as Bad Request, Internal Server Error, and assert that the exception handler method should be invoked to send a client-friendly message. I am able to successfully mock the response of external API as Bad Request but it is not throwing the HttpStatusCodeException which is ideally thrown for 4xx status code and how can I verify method invocation of exception handler
private final RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
private final HttpHeaders httpHeaders = new HttpHeaders();
private final NotificationServiceImpl notificationService = new NotificationServiceImpl(restTemplate, httpHeaders, NOTIFICATION_API_URL, PRIMARY_NOTIFIERS, CC_NOTIFIERS, LANG, APPLICATION_NAME);
#Autowired
private ExceptionTranslator exceptionTranslator;
#Test
void testErrorOnSendNotification() {
Map<String, Instant> messages = Map.of("sample message", Instant.now());
ResponseEntity<HttpStatusCodeException> responseEntity =
new ResponseEntity<>(HttpStatus.BAD_REQUEST);
when(restTemplate.exchange(
ArgumentMatchers.anyString(),
ArgumentMatchers.any(HttpMethod.class),
ArgumentMatchers.any(),
ArgumentMatchers.<Class<HttpStatusCodeException>>any()))
.thenReturn(responseEntity);
// assertThrows(HttpStatusCodeException.class, () -> notificationService.sendNotification(messages));
verify(exceptionTranslator, times(1)).handleExceptions(any(), any());
}
#ExceptionHandler(Exception.class)
public ResponseEntity<Problem> handleExceptions(NativeWebRequest request, Exception error) {
Problem problem =
Problem.builder()
.withStatus(Status.BAD_REQUEST)
.withTitle(error.getMessage())
.withDetail(ExceptionUtils.getRootCauseMessage(error))
.build();
return create(error, problem, request);
}
You are mocking the restTemplate response. The actual #ExceptionHandler is not called at all. You are bypassing that layer.
In your case, in order to verify the ExceptionHandler, your service layer can be mocked, but the actual REST call has to proceed through, and a REAL response has to be triggered, in order for you to verify the Response Status Code + message.
Psuedo Code below:
#Service
class Service{
public void doSomeBusinessLogic() throws SomeException;
}
#RestController
class ControllerUsingService{
#AutoWired
private Service service;
#POST
public Response somePostMethidUsingService() throws SomeException{
service.doSomeBusinessLogic(someString);
}
}
#Test
void testErrorOnSendNotification() {
when(service.doSomeBusinessLogic(anyString()))
.thenThrow(SomeExceptionException.class);
Response receivedResponse = restTemplate.post(request, headers, etc);
//assert receivedResponse status code + message.
}
Hope that makes sense,
For further clarification:
By doing:
ResponseEntity<HttpStatusCodeException> responseEntity =
new ResponseEntity<>(HttpStatus.BAD_REQUEST);
when(restTemplate.exchange(
ArgumentMatchers.anyString(),
ArgumentMatchers.any(HttpMethod.class),
ArgumentMatchers.any(),
ArgumentMatchers.<Class<HttpStatusCodeException>>any()))
.thenReturn(responseEntity);
You are bypassing service layer and actually stating that whenever I make a request towards /API/xyz, then I should receive a BAD_REQUEST. That means whatever exception handling you have is going to be bypassed.

ServerResponseFilter not being applied to GET

I have a Quarkus application with the following filters definition:
#ApplicationScoped
#Slf4j
public class Filters {
// some #Inject parameters i'm using
#ServerRequestFilter(preMatching = true)
public void requestLoggingFilter(ContainerRequestContext requestContext) {
log.info("Recv: [{}] {}, {}", requestContext.getHeaderString("myHeader"), requestContext.getMethod(), requestContext.getUriInfo().getRequestUri());
}
#ServerResponseFilter
public void responseBasicHeaderFilter(ContainerResponseContext responseContext) {
responseContext.getHeaders().putSingle("myHeader, "myValue");
}
#ServerResponseFilter
public void responseLoggingFilter(ContainerResponseContext responseContext) {
log.info("Sent: [{}] {} {}", responseContext.getHeaderString("myHeader"), , responseContext.getStatusInfo(), responseContext.getEntity());
}
}
And I have two tests:
Test Class config:
#QuarkusTest
public class MyTest {
...
}
Test A:
final Response response = given()
.post(BASE_URL)
.then()
.extract().response();
assertEquals(200, response.getStatusCode(), () -> "Got: " + response.prettyPrint());
assertEquals("myValue", response.getHeader("myHeader"));
final Response response2 = given()
.get(BASE_URL)
.then()
.extract().response();
assertEquals(200, response2.getStatusCode(), () -> "Got: " + response2.prettyPrint());
assertEquals("myValue", response2.getHeader("myHeader"));
Test B:
final Response response = given()
.post(BASE_URL)
.then()
.extract().response();
assertEquals(200, response.getStatusCode(), () -> "Got: " + response.prettyPrint());
assertEquals("myValue", response.getHeader("myHeader"));
If i run Test B on it's own, it passes.
If i run Test A however the last assertion fails (the header value is not there).
The #ServerResponseFilter seem to not be called beyond the first time, however #ServerRequestFilter seem to be fine.
I have tested the api manually and can confirm the same behaviour. Calling the GET request first will also have the same behaviour.
I have verified that the response generated by my Controller (pojo) is generated successfully.
What could be preventing it from being rerun?
Turns out it wasn't related to GET vs POST
my GET method was returning a Multi . I converted this to Uni> and it worked.
From the documentation i found this snippet
Reactive developers may wonder why we can't return a stream of fruits directly. It tends to eb a bad idea with a database....
The keyword being we can't so I imagine this is not supported functionality

MvcResult has a status of 200 including the response but the assertion fails

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!

When the RestApi has ResponseEntity<void> of spring has return value it returns ClientProtocolException

I have a RestApi exposed which on case returns status ok back to client.
The method signature of the method is ResponseEntity<void> methodName(){}.
This method is a deleteApi.
In the return responseEntity is created with just Status OK and no body or any other header details are appended.
Seen in the logs that I get ClientProtocolException when the call is made , saw that when the same is executed through the REST client (postman) received correct Status OK message in the response.
What are the reasons when the ClientProtocolException is raised?
If the return type is ResponeEntity<Void> is it mandatory to send body with it ?
How do i avoid getting the above exception?
Code:
#RequestMapping(method = RequestMethod.DELETE)
public ResponseEntity<Void> methodName()
{
// your business logic
return new ResponseEntity<Void>(HttpStatus.OK);
}
Try as below
#RequestMapping(method = RequestMethod.DELETE)
public ResponseEntity<Void> methodName() {
// your business logic
return ResponseEntity.noContent().build();
}

spring MockMvc testing for model attribute

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...

Categories

Resources