i am trying to write the mock test case for RestServiceImpl class. Below is the code. And i have a test class shown below RestServiceImplTest. When i run the test class it returning null from line restTemplate.exchange(UrlString, HttpMethod.POST, request, Object.class)
public class RestServiceImpl
private RestTemplate restTemplate;
#Autowired
public RestServiceImpl(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
#Override
public ResponseEntity<Object> restService(DataRequest dataRequest) throws Exception {
ResponseEntity<Object> response = null;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<DataRequest> request = new HttpEntity<>(dataRequest, headers);
try {
response = restTemplate.exchange(UrlString, HttpMethod.POST, request, Object.class);
} catch (Exception ex) {
throw ex;
}
return response;
}
}
Test class
#RunWith(SpringRunner.class)
public class RestServiceImplTest {
private RestServiceImpl restServiceImpl;
#Mock
private RestTemplate restTemplate;
#Mock
private DataRequest dataRequest;
#Before
public void setUp() {
restServiceImpl = new RestServiceImpl(restTemplate);
dataRequest = new DataRequest();
}
#Test
public void testRestServiceImplwithSuccess() throws Exception {
ResponseEntity<Object> resp = new ResponseEntity<Object>(HttpStatus.OK);
doReturn(resp).when(restTemplate).exchange(any(URI.class), any(HttpMethod.class), any(HttpEntity.class),
any(Class.class));
ResponseEntity<Object> mockResp = restServiceImpl.restService(DataRequest);
}
Can anybody tell me where is it going wrong.
Almost certainly this is happening because your doReturn is not being used due to the parameters not being met (your any() s).
Related
I am new to JUnit and Mockito. Here I am trying to mock the rest template call and return a response entity. But it throws me a Null pointer Exception. I am not able to figure out what's wrong with the mocking. Can anyone guide me where I am doing wrong?
JUnit5 Unit test cases
class MyTest {
#InjectMocks
private MyService service;
#Mock
private RestTemplate template;
#BeforeEach
void setUp(){
MockitoAnnotations.initMocks(this);
}
private ResponseEntity<String> generateResponse(String body, HttpStatus http) {
return new ResponseEntity<>(body, http);
}
#Test
public void publishEventsuccessResponseTest() {
when(template.postForEntity(ArgumentMatchers.anyString(), ArgumentMatchers.any(),
ArgumentMatchers.<Class<String>>any())).thenReturn(generateResponse("uuid",
HttpStatus.OK));
String result = service.sentData("data");
Assertions.assertTrue("uuid".equals(result));
}
}
Service
class MyService {
public String sentData(String data) {
String jsonResp = "";
ResponseEntity<String> response;
try {
HttpEntity<String> requestBody = new HttpEntity<String>(data, headers);
response = restTemplate.postForEntity("url", requestBody, String.class);
} catch (Exception ex) {}
System.out.println(response); //value : Null
if (response.getStatusCodeValue() == 200) { // Null pointer exception
jsonResp = response.getBody();
}
return jsonResp;
}
}
Change the #BeforeEach to #Before of jUnit4 and add public void setUp()
Your test should be green!
i'm working in spring project where i have a service that call another api using Restemplate , this service return just a string as a token and all works fine for me , this is my service :
#Service
public class AppServiceImpl {
#Value("${rest.appUrl}")
private String appUrl;
#Value("${credType}")
private String credType;
#Autowired
private RestTemplate restTemplate;
public String getToken() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", credType);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<TargetObject> response = restTemplate.exchange(appUrl, HttpMethod.POST, request, TargetObject.class);
TargetObject targetObject = response.getBody();
return "Bearer " + targetObject.getToken();
}
}
my problem is when i want to unit test this service i'm getting NullPointerException and i dont know why , this is my unit test :
#RunWith(SpringRunner.class)
public class AppServiceImplTest {
private AppService appService = new AppServiceImpl();
#Test
public void getTokenTest() {
String token = appService.getToken();
assertTrue(token != null);
}
}
the NullPointerException in this line :
ResponseEntity<TargetObject> response = restTemplate.exchange(appUrl, HttpMethod.POST, request, TargetObject.class);
do you have any idea what's the problem with my test ? i spent hours without any result
Thanks in advance
your problem is that in your service you autowire restTemplate, but in your test you initialize your class by yourself, and not through spring, and there for it is not initialized and remains null.
if you are initializing the service by yourself in the test, make sure to add a mocked instance of restTemplate. I suggest moving the autowire in the service to be c'tor based, and then it will be easy to do so in the test:
#Service
public class AppServiceImpl {
private String appUrl;
private String credType;
private RestTemplate restTemplate;
public AppServiceImpl(RestTemplate restTemplate, #Value("${credType}") String credType, #Value("${rest.appUrl}") String appUrl) {
this.restTemplate = restTemplate;
this.credType = credType;
this.appUrl = appUrl;
}
...
...
...
}
and later in your test:
#Mock private RestTemplate restTamplate;
private AppService appService;
#Before
public void setup() {
appService = new AppServiceImpl(restTamplate);
}
Use Mockito It will resolve your issue.
ResponseEntity<TargetObject> response = restTemplate.exchange(appUrl, HttpMethod.POST,
request, TargetObject.class);
junit for above code would be:
#Mock
private RestTemplate restTemplate;
TargetObject targetObject = new TargetObject();
targetObject.setToken("123456");
Mockito.when( restTemplate.exchange(Mockito.anyString(),Mockito.any(HttpMethod.class),
Mockito.any(HttpEntity.class),Mockito.any(TargetObject.class)).thenReturn(targetObject);
I am consuming API which has to type of response success response 200 and Bad response 400 both of them has parameters inside their response body but the problem is am not able to get the bad response parameters it throws this exception
public ResponseEntity<String> balanceInquiry(BalanceInquiryRequestDto balanceInquiryRequestDto) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
httpHeaders.set("API-KEY", "5d6f54d4");
HttpEntity<BalanceInquiryRequestDto> request = new HttpEntity<BalanceInquiryRequestDto>(balanceInquiryRequestDto , httpHeaders);
ResponseEntity<String> postForEntity =
restTemplate.postForEntity(uri , request, String.class);
return postForEntity;
}
it is working good when the response is ok 200
I created a small spring boot project to showcase what you can do.
First a simple service that will give us an error when called:
#RestController
public class Endpoint {
#GetMapping("/error")
public ResponseEntity createError() {
ErrorDetails errorDetails = new ErrorDetails("some error message");
return ResponseEntity.status(400).body(errorDetails);;
}
}
The error details which you want to extract are similar to this in this example:
#AllArgsConstructor
#Getter
#Setter
#ToString
#EqualsAndHashCode
public class ErrorDetails {
private String errorMessage;
}
And then another endpoint with a client that calls the failing service. It returns the error details received:
#RestController
public class ClientDemo {
#Autowired
private RestTemplate restTemplate;
#GetMapping("/show-error")
public String createError() {
try{
return restTemplate.getForEntity("http://localhost:8080/error", String.class).getBody();
} catch(HttpClientErrorException | HttpServerErrorException ex) {
return ex.getResponseBodyAsString();
}
}
}
For sake of completion:
#SpringBootApplication
public class StackoverflowApplication {
public static void main(String[] args) {
SpringApplication.run(StackoverflowApplication.class, args);
}
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
When navigating to http://localhost:8080/show-error you see this:
{
"errorMessage": "some error message"
}
I have created a Spring Restful Service and Spring MVC application.
Restful Service ::
Restful service returns an entity if its existing in DB. If it doesn't exist It returns a custom Exception information in ResponseEntity object.
It is working as expected tested using Postman.
#GetMapping(value = "/validate/{itemId}", produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public ResponseEntity<MyItem> validateItem(#PathVariable Long itemId, #RequestHeader HttpHeaders httpHeaders) {
MyItem myItem = myitemService.validateMyItem(itemId);
ResponseEntity<MyItem> responseEntity = null;
if (myItem == null) {
throw new ItemNotFoundException("Item Not Found!!!!");
}
responseEntity = new ResponseEntity<MyItem>(myItem, headers, HttpStatus.OK);
return responseEntity;
}
If the requested Entity does not exist Restful Service returns below.
#ExceptionHandler(ItemNotFoundException.class)
public ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {
System.out.println("In CREEH::ItemNFE");
ExceptionResponse exceptionResponse = new ExceptionResponse("Item Not Found Ex!!!", new Date(), webRequest.getDescription(false));
ResponseEntity<ExceptionResponse> responseEntity = new ResponseEntity<ExceptionResponse>(exceptionResponse, HttpStatus.NOT_FOUND);
return responseEntity;
}
But when I am calling the above service from a spring MVC application using RestTemplate, It is returning a valid object if it exists.
If the requested object does not exist Restful service is returning the exception information but its not reaching the calling(spring MVC) application.
Spring MVC application calls Restful Web Service using Rest template
String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";
ResponseEntity<Object> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Object.class, uriParms);
int restCallStateCode = responseEntity.getStatusCodeValue();
This is expected behavior. Rest template throws exception when the http status is client error or server error and returns the response when http status is not error status.
You have to provide implementation to use your error handler, map the response to response entity and throw the exception.
Create new error exception class with ResponseEntity field.
public class ResponseEntityErrorException extends RuntimeException {
private ResponseEntity<ErrorResponse> errorResponse;
public ResponseEntityErrorException(ResponseEntity<ErrorResponse> errorResponse) {
this.errorResponse = errorResponse;
}
public ResponseEntity<ErrorResponse> getErrorResponse() {
return errorResponse;
}
}
Custom error handler which maps the error response back to ResponseEntity.
public class ResponseEntityErrorHandler implements ResponseErrorHandler {
private List<HttpMessageConverter<?>> messageConverters;
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return hasError(response.getStatusCode());
}
protected boolean hasError(HttpStatus statusCode) {
return (statusCode.is4xxClientError() || statusCode.is5xxServerError());
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
HttpMessageConverterExtractor<ExceptionResponse> errorMessageExtractor =
new HttpMessageConverterExtractor(ExceptionResponse.class, messageConverters);
ExceptionResponse errorObject = errorMessageExtractor.extractData(response);
throw new ResponseEntityErrorException(ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).body(errorObject));
}
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = messageConverters;
}
}
RestTemplate Configuration - You have to set RestTemplate's errorHandler to ResponseEntityErrorHandler.
#Configuration
public class RestTemplateConfiguration {
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
ResponseEntityErrorHandler errorHandler = new ResponseEntityErrorHandler();
errorHandler.setMessageConverters(restTemplate.getMessageConverters());
restTemplate.setErrorHandler(errorHandler);
return restTemplate;
}
}
Calling Method
#Autowired restTemplate
String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";
try {
ResponseEntity<Object> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Object.class, uriParms);
int restCallStateCode = responseEntity.getStatusCodeValue();
} catch (ResponseEntityErrorException re) {
ResponseEntity<ErrorResponse> errorResponse = re.getErrorResponse();
}
Try using the #ResponseBody annotation on your Exceptionhandler. e.g:
public #ResponseBody ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {... }
You should use Custom Exception Handler to fix your case. It looks like this
#ControllerAdvice
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
public CustomResponseEntityExceptionHandler() {
super();
}
// 404
#ExceptionHandler(value = { EntityNotFoundException.class, ResourceNotFoundException.class })
protected ResponseEntity<Object> handleNotFound(final RuntimeException ex, final WebRequest request) {
BaseResponse responseError = new BaseResponse(HttpStatus.NOT_FOUND.value(),HttpStatus.NOT_FOUND.name(),
Constants.HttpStatusMsg.ERROR_NOT_FOUND);
logger.error(ex.getMessage());
return handleExceptionInternal(ex, responseError, new HttpHeaders(), HttpStatus.NOT_FOUND, request);
}
}
And your code should throw some exception, eg:
if (your_entity == null) {
throw new EntityNotFoundException("said something");
}
If you get this case in somewhere else again, you just throw exception like above. Your handler will take care the rest stuffs.
Hope this help.
I've started your application and works just fine.
Maven :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
The controller class is :
#Controller
public class ValidationController {
#GetMapping(value = "/validate/{itemId}")
public #ResponseBody ResponseEntity<MyItem> validateItem(#PathVariable Long itemId) {
if (itemId.equals(Long.valueOf(1))) {
throw new ItemNotFoundException();
}
return new ResponseEntity<>(new MyItem(), HttpStatus.OK);
}
#ExceptionHandler(ItemNotFoundException.class)
public ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {
System.out.println("In CREEH::ItemNFE");
ExceptionResponse exceptionResponse = new ExceptionResponse("Item Not Found Ex!!!", new Date(), webRequest.getDescription(false));
ResponseEntity<ExceptionResponse> responseEntity = new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
return responseEntity;
}
}
and the test:
#RunWith(SpringRunner.class)
#WebMvcTest(value = ValidationController.class, secure = false)
public class TestValidationController {
#Autowired
private MockMvc mockMvc;
#Test
public void testExpectNotFound() throws Exception {
mockMvc.perform(get("/validate/1"))
.andExpect(status().isNotFound());
}
#Test
public void testExpectFound() throws Exception {
mockMvc.perform(get("/validate/2"))
.andExpect(status().isOk());
}
}
Are you sure the url you are trying to use with RestTemplate is correct?
String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";
Your get method is #GetMapping(value = "/validate/{itemId}"
If you don't have request mapping at the level of the controller the url should be:
http://localhost:8080/validate/1
Another difference is the missing #ResponseBody on your controller method.
I am testing an #RestController which has an API endpoint such as /api/dataobject. If the object (in JSON format) that is posted to this endpoint is missing some part of its meta data, the API should respond with a Http status of bad request (400).
When testing it through Postman, this works, however in my unit test where the controller is mocked it still returns a status 200.
The method in the RestController:
#RequestMapping("/api/dataobject")
public ResponseEntity postDataObject(#RequestBody final DataObject dataObject) throws InvalidObjectException {
if (!dataObjectValidator.validateDataObject(dataObject)) {
throw new InvalidObjectException("Data object was invalid: " + dataObject.toString());
}
return new ResponseEntity(HttpStatus.OK);
}
The InvalidObjectException is caught by a class annotated with #ControllerAdvice which extends ResponseEntityExceptionHandler and is handled as follows:
#ExceptionHandler(value = InvalidObjectException.class)
protected ResponseEntity<Object> handleInvalidObject(final InvalidObjectException exception, final WebRequest request) {
final String bodyOfResponse = exception.getMessage();
return handleExceptionInternal(exception, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}
Now, the unit test class is as follows:
#RunWith(SpringRunner.class)
#WebMvcTest(DataObjectController.class)
public class DataObjectControllerTest {
#Autowired
private MockMvc mvc;
#MockBean
private DataObjectController dataObjectController;
private final String uri = "/api/idataobject";
#Test
public void noAppName() throws Exception {
DataObject object = getDataObjectNoAppName();
final String body = new Gson().toJson(object);
given(dataObjectController.postDataObject(object)).willReturn(new ResponseEntity(HttpStatus.BAD_REQUEST));
mvc.perform(post(uri)
.content(body)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest());
}
}
Even though the object is invalid, and I've said that the given object would return a HttpStatus 400, I get a 200 status in return.
Clearly I'm missing something here, but what?