Hey I have service that throws exception when user is not found, but when I am trying to test method it doesnt work as expected, it works when I test in postman and the message is right with 400 error, but test doesnt seem to work..
#Test
#WithMockUser(username = "admin", authorities = {"ADMIN"})
public void shoulThrowExceptionGettingUser() throws Exception {
doThrow(new ResponseStatusException(HttpStatus.NOT_FOUND))
.when(userService).getUser("TEST");
MvcResult s = mvc.perform(get("/api/user")
.contentType(MediaType.APPLICATION_JSON)
.param("username","Test")).andDo(print())
.andExpect(status().isBadRequest()).andReturn();
}
public UserDtoNoPass getUser(String userName)
{
try {
UserDetails userDetails= manager.loadUserByUsername(userName);
UserDtoNoPass toReturn= new UserDtoNoPass();
toReturn.setUserName(userDetails.getUsername());
toReturn.setList(userDetails.getAuthorities().stream().map(s->s.toString()).map(
s->Enum.valueOf(Authority.class,s)).collect(Collectors.toList()));
return toReturn;
}
catch (UsernameNotFoundException e)
{
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
}
}
#GetMapping
public ResponseEntity<Object> getUser(#RequestParam("username") String username)
{
HttpHeaders headers= new HttpHeaders();
try{
UserDtoNoPass dto=userService.getUser(username);
headers.add("successfull","true");
return new ResponseEntity<>(dto,headers, HttpStatus.OK);
}
catch (Exception e)
{
headers.add("successfull","false");
return new ResponseEntity<>("No such a user found",headers, HttpStatus.BAD_REQUEST);
}
}
The mock is set up for a String with value "TEST":
doThrow(new ResponseStatusException(HttpStatus.NOT_FOUND))
.when(userService).getUser("TEST");
but the actual call uses a String with value "Test":
MvcResult s = mvc.perform(get("/api/user")
...
.param("username","Test")
...
Both Strings should have the same value. I personally like to keep mock setup and mock validation separate. Thus I would advise to rewrite the test to:
doThrow(new ResponseStatusException(HttpStatus.NOT_FOUND))
.when(userService).getUser(anyString());
...
MvcResult s = mvc.perform(get("/api/user")
...
.param("username","Test")
...
...
verify(userService).getUser("Test");
As an aside: in genereal, the mock setup should use the form
when(...).then(Return|Throw)(...)
instead of
do(Return|Throw)(...).when(...)...
since the former guarantees type safety. The latter should only be used if not possible otherwise (e.g. throwing an Exception on a void method).
With this in mind, we could rewrite the mock setup to
when(userService.getUser(anyString()))
.thenThrow(new ResponseStatusException(HttpStatus.NOT_FOUND));
Related
I'm trying to run some integration tests on my code and I use a MockRestServiceServer from spring-boot-test in order to set up the expected requests.
I have one call that is called many times while running my test, but it seems not to persist during the test. My test looks like this:
#Test
void getHealthStatus() {
try {
RequestBuilder request = get("/actuator/hc").contentType("application/json");
MockServerBinder.bindPersistentThingworxPropertiesCall(
mockServer,
requestTo(new URI(String.format("%sProperties/TestProp", thingworxUrl))),
objectMapper.writeValueAsString(new PingResponse(DashboardIndicator.HEALTHY, 200))
);
DashboardStatusModel expectedResult = new DashboardStatusModel();
expectedResult.addResult("spring",service.getAppHealth());
expectedResult.addResult("thingworx", service.getThingworxAvailability());
assertOpenUrl(request);
MvcResult result = mockMvc.perform(get("/actuator/hc").contentType("application/json"))
.andExpect(status().isOk())
.andReturn();
DashboardStatusModel actualResult = objectMapper.readValue(result.getResponse().getContentAsString(), DashboardStatusModel.class);
assertEquals(expectedResult.getResults().get("spring"), actualResult.getResults().get("spring"));
assertEquals(expectedResult.getResults().get("thingworx").getStatus(),actualResult.getResults().get("thingworx").getStatus());
assertEquals(expectedResult.getResults().get("thingworx").getData().get("url"), actualResult.getResults().get("thingworx").getData().get("url"));
} catch (Exception e) {
fail("Unable to perform REST call on GDP-API", e);
}
}
As additional information:
mockServer is created in a superclass like this:
protected static MockRestServiceServer mockServer;
#BeforeEach
public void configureMockServer() {
mockServer = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();
}
MockServerBinder.bindPersistentThingworxPropertiesCall() is a helper class that looks like this:
public static void bindPersistentThingworxPropertiesCall(MockRestServiceServer mockServer, RequestMatcher request, String responseJSONasString){
mockServer.expect(ExpectedCount.once(), request)
.andExpect(method(HttpMethod.GET))
.andRespond(withStatus(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(responseJSONasString));
}
assertOpenUrl(request); is a function that checks if a URL doesn't have any authentication by using a MockMVC:
public void assertOpenUrl(RequestBuilder request){
try{
mockMvc.perform(request).andExpect(status().isOk());
} catch (Exception e) {
fail("Unable to perform REST call on GDP-API", e);
}
}
When I run this test, the expectedResult.addResult("thingworx", service.getThingworxAvailability()); will be able to use the MockRestServiceServer, but the assertOpenUrl(request); line will fail, because MockRestServiceServer doesn't expect anymore calls to the endpoint binded in MockServerBinder.bindPersistantThingworxPropertyCall(). This does not happen if I Copy & Paste MockServerBinder.bindPersistantThingworxPropertyCall() under the existing one, so I think it's a problem with how I binded the request in the first place.
From what I understand ExpectedCount.manyTimes() should keep this request during the test.
Is this not true or is there another way I should bind my request so that it stays available during the entire test?
PEBCAK issue.
As you can see in the bindPersistentThingworxPropertiesCall(), I actually didn't use ExpectedCount.manyTimes(). I didn't catch that. Changed it and now it works.
I am writing Junit for a method which has two methods, which has two different Rest calls but each rest call returns different response object.
When I try to mock this behavior using doReturn(responsetype1).when(restcall1), first rest call gets success with return type as responsetype1 but for second method doReturn(responsetype2).when(restcall2) fails because doReturn(responsetype2) is not working instead it is still giving responsetype1. Someone please help with this
public mainResponse add(HttpServletRequest request, String string1, HttpHeaders headers,
List<String> list) {
boolean deleteStatus = method1(request, string1);
if (!deleteStatus) {
collectionResponse = method2(string1, colId, headers);
}
}
public boolean method1(HttpServletRequest request, String string1) throws Exception {
ResponseEntity<ResponseType1> responseType1 = restTemplate.exchange(url1, HttpMethod.GET, new HttpEntity<>(headers),
ResponseType1.class);
//perfrom certain operation and return response
return status;
}
ResponseType2 method2(String string1, String colId , HttpHeaders headers) {
ResponseEntity<ResponseType2> response = restTemplate.exchange(url2, HttpMethod.POST,
new HttpEntity<>(body, headers), ResponseType2.class);
return response.getBody(); //**this line is failing because ResponseType1 is being returned here**
}
My test class :
#Test
public void test() throws Exception {
doReturn(**ResponseType1**).when(restTemplate).exchange(ArgumentMatchers.anyString(), ArgumentMatchers.any(HttpMethod.class),
ArgumentMatchers.any(), ArgumentMatchers.<Class<ResponseType1>>any());
doReturn(**ResponseType2**).when(restTemplate).exchange(ArgumentMatchers.anyString(), ArgumentMatchers.any(HttpMethod.class),
ArgumentMatchers.any(), ArgumentMatchers.<Class<ResponseType2>>any());
//other operation
}
When the URL is wrong or something wrong with the response during third party API call in getMethodWithHeader this method will throw HttpClientErrorException so how to write a test case for this
This is my main code method
public JSONObject callRespectiveAPI(String url, String apiKeyAndPassword) {
JSONObject result = new JSONObject();
try {
String accessToken = apiUrlUtil.getAccessToken(apiKeyAndPassword);
ResponseEntity<String> response = apiUrlUtil.getMethodWithHeader(url, accessToken);
String nextUrl = apiUrlUtil.getNextUrl(response.getHeaders());
result = JSONObject.fromObject(response.getBody());
result.put("nextUrl", nextUrl);
} catch(HttpClientErrorException e) {
result.put("status", "404");
result.put("message", "Not Found");
LOGGER.error(e.getMessage());
}
return result;
}
I want to throw HttpClientErrorException and test it
This is the test code
#Test
public void callRespectiveAPITest2() {
JSONObject object = new JSONObject();
object.put("success", true);
ResponseEntity<String> response = new ResponseEntity<String>(object.toString(), HttpStatus.OK);
when(apiUrlUtil.getAccessToken(Mockito.anyString())).thenReturn("accessToken");
when(apiUrlUtil.getMethodWithHeader(Mockito.anyString(), Mockito.anyString())).thenReturn(response);
when(apiUrlUtil.getNextUrl(Mockito.any())).thenReturn("nextUrl");
assertEquals(true, shopifyService.callRespectiveAPI("nextUrl", "accessToken").get("success"));
}
You are almost there:
when(apiUrlUtil.getNextUrl(Mockito.any())).thenReturn("nextUrl");
you can turn that into
when(apiUrlUtil.getNextUrl(Mockito.any())).thenThrow( ... )
and have that throw whatever you want.
Of course, you also want to adapt the assertions accordingly, for example to check for that 404 status and "Not Found" message.
But note, the real answer here is: read the documentation, and do your own research.
I am writing a test to test the POST method for failure case in the controller.
It returns a 415, I am expecting 500. I have mocked the response using mockito.
ControllerTest
#Test
#DisplayName("POST /customers - Failure")
void createProductShouldFail() throws Exception {
// Setup mocked service
when(customerService.save(any(Customer.class))).thenThrow(HttpServerErrorException.InternalServerError.class);
RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/customers").accept(MediaType.APPLICATION_JSON)
.content("{\"name\":\"John\"}");
MvcResult result=mockMvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response = result.getResponse();
// Validate the response code and content type
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), response.getStatus());
}
Controller
#PostMapping(path = "/customers")
public ResponseEntity<Customer> saveCustomer(#RequestBody Customer customer){
try {
// Create the new product
Customer savedCustomer = customerService.save(customer);
// Build a created response
return ResponseEntity
.created(new URI("/customers/" + savedCustomer.getId()))
.body(savedCustomer);
} catch (URISyntaxException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
Error:
HTTP Method = POST
Request URI = /customers
Parameters = {}
Headers = [Accept:"application/json", Content-Length:"15"]
Body = {"name":"John"}
Session Attrs = {}
Handler:
Type = com.prabhakar.customer.controller.CustomerController
Method = com.prabhakar.customer.controller.CustomerController#saveCustomer(Customer)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.HttpMediaTypeNotSupportedException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 415
Error message = null
Headers = [Accept:"application/json, application/*+json"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
org.opentest4j.AssertionFailedError:
Expected :500
Actual :415
But 415-Unsupported Media Type client error response code.
I have used the same payload for this method,it works.
#Test
#DisplayName("POST /customers - Success")
void createProductShouldSucceed() throws Exception {
// Setup mocked service
Customer mockCustomer = new Customer(1L, "John");
when(customerService.save(any(Customer.class))).thenReturn(mockCustomer);
this.mockMvc.perform(post("/customers")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\":\"John\"}"))
// Validate the response code and content type
.andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
//Validate returned json fields
.andExpect(jsonPath("$.id").value(1L))
.andExpect(jsonPath("$.name").value("John"));
}
Update I have added
#RestController
#EnableWebMvc
this throws an error as mocked But the code breaks near mockmvc.perform.
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.web.client.HttpServerErrorException$InternalServerError
How can I verify if this is working.
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), response.getStatus());
There are two thing you must have in account to solve the problem:
First, Instead of use .accept(MediaType.APPLICATION_JSON) you must use .contentType(MediaType.APPLICATION_JSON).
Second, the other thing you must have in account is, if you are not handling the exception (using a controller advice or other way) you must do it, because when you execute the firts step you will receive the following error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.web.client.HttpServerErrorException$InternalServerError
The workaround that I took was use #ExceptionHandler in the CustomerController to test your code (this isn't the best place to do this, depending what you are doing. Instead use a #ControllerAdvice. You can find some examples here https://www.baeldung.com/exception-handling-for-rest-with-spring).
Below the complete code that are used to recreate your case.
Customer.class
public class Customer {
private Long id;
private String name;
public Customer(Long id, String name) {
this.id = id;
this.name = name;
}
// + getter and setter
}
CustomerController.class
#RestController
public class CustomerController {
private final CustomerService customerService;
public CustomerController(CustomerService customerService) {
this.customerService = customerService;
}
#PostMapping(path = "/customers")
public ResponseEntity<Customer> saveCustomer(#RequestBody Customer customer) {
try {
// Create the new product
Customer savedCustomer = customerService.save(customer);
// Build a created response
return ResponseEntity
.created(new URI("/customers/" + savedCustomer.getId()))
.body(savedCustomer);
} catch (URISyntaxException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
// Code used to avoid the error explained in the second step
#ExceptionHandler
public ResponseEntity<?> handlingInternalServerError(HttpServerErrorException.InternalServerError ex) {
// code to be executed when the exception is thrown (logs, ...)
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
CustomerService.class
#Service
public class CustomerService {
public Customer save(Customer customer) {
return customer;
}
}
CustomerControllerTest.class
#SpringBootTest
#AutoConfigureMockMvc
class CustomerControllerTest {
#MockBean
private CustomerService customerService;
#Autowired
private MockMvc mockMvc;
#Test
#DisplayName("POST /customers - Failure")
void saveCustomer() throws Exception {
Customer customerMock = new Customer(1L, "John");
// Setup mocked service
when(customerService.save(any(Customer.class))).thenThrow(HttpServerErrorException.InternalServerError.class);
RequestBuilder requestBuilder = post("/customers")
.content("{\"name\":\"John\"}")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response = result.getResponse();
// Validate the response code and content type
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), response.getStatus());
}
}
NOTE: This test was executed using Java 8 and JUnit5
Other NOTE based on your comment:
Ok. #prabhakar-maity, my recommendation based in your case is to use a #ExceptionHandler or #ControllerAdvice instead of try...catch. For example, you have 6 methods in your controller or several controllers and want to handle the same exception (Internal Server Error) and return the same info, so you’ll have to implement a try..catch in each method, while using #ControllerAdive (multiple controllers) or #ExceptionHandler (one controller) you implement your logic in one place
Check this question for more info LINK
You can reference Spring MVC Test Framework - Unsupported Media Type
You may be missing #EnableWebMvc annotation in your controller.
EDIT - for Comment:
Instead of
RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/customers").accept(MediaType.APPLICATION_JSON)
.content("{\"name\":\"John\"}");
MockHttpServletResponse response = result.getResponse();
// Validate the response code and content type
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(),
response.getStatus());
Try:
mockMvc.perform(requestBuilder)
.andExpect(status().isInternalServerError());
I try some tests on my UserController for updating the email field like this :
#Test
public void testUpdateUserNominal() throws Exception {
savedUser.setEmail(EMAIL_2);
UserDTORequest updatedUserDTORequest = modelMapper.map(savedUser, UserDTORequest.class);
String jsonMessage = gson.toJson(updatedUserDTORequest);
mvc.perform(put(USER_ROUTE)
.contentType(APPLICATION_JSON)
.accept(APPLICATION_JSON)
.content(jsonMessage))
.andExpect(status().isOk())
.andReturn();
assertThat(userService.findAll().size()).isEqualTo(1);
assertThat(userService.findAll().get(0).getEmail()).isEqualTo(EMAIL_2);
}
Here is the code on my controller :
#Override
public ResponseEntity<UserDTOResponse> update(UserDTORequest userDTORequest) throws ParseException {
Optional<User> optUser = userService.findByUri(userDTORequest.getUri());
if(optUser.isEmpty()){
return ResponseEntity
.notFound()
.build();
}
User user = convertToEntity(userDTORequest);
User userUpdated = userService.save(user);
UserDTOResponse userDTOResponse = convertToDto(userUpdated);
return ResponseEntity
.ok(userDTOResponse);
}
The response from the mockMvc is correct : the new email set is the good one.
But on the second assertThat :
assertThat(userService.findAll().get(0).getEmail()).isEqualTo(EMAIL_2);
The email is not the good one, the email is not updated.
What I do wrong ?
Thanks :)