How to Mock rest service - spring-boot

I'm wondering how do I mock the rest controller for the code below,
public void sendData(ID id, String xmlString, Records record) throws ValidationException{
ClientHttpRequestFactory requestFactory = new
HttpComponentsClientHttpRequestFactory(HttpClients.createDefault());
RestTemplate restTemplate = new RestTemplate(requestFactory);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
restTemplate.setMessageConverters(messageConverters);
MultiValueMap<String,String> header = new LinkedMultiValueMap<>();
header.add("x-api-key",api_key);
header.add("Content-Type",content_type);
header.add("Cache-Control",cache_control);
HttpEntity<String> request = new HttpEntity<>(xmlString, header);
try {
restTemplate.postForEntity(getUri(id,record), request, String.class);
}catch (RestClientResponseException e){
throw new ValidationException("Error occurred while sending a file to some server "+e.getResponseBodyAsString());
}
}
Any suggestion would be helpful.
I tried to do something like this,
#RunWith(MockitoJUnitRunner.class)
public class Safe2RestControllerTest {
private MockRestServiceServer server;
private RestTemplate restTemplate;
private restControllerClass serviceToTest;
#Before
public void init(){
//some code for initialization of the parameters used in controller class
this.server = MockRestServiceServer.bindTo(this.restTemplate).ignoreExpectOrder(true).build();
}
#Test
public void testSendDataToSafe2() throws ValidationException, URISyntaxException {
//some code here when().then()
String responseBody = "{\n" +
" \"responseMessage\": \"Validation succeeded, message
accepted.\",\n" +
" \"responseCode\": \"SUCCESS\",\n" +
" 2\"responseID\": \"627ccf4dcc1a413588e5e2bae7f47e9c::0d86869e-663a-41f0-9f4c-4c7e0b278905\"\n" +
"}";
this.server.expect(MockRestRequestMatchers.requestTo(uri))
.andRespond(MockRestResponseCreators.withSuccess(responseBody,
MediaType.APPLICATION_JSON));
serviceToTest.sendDataToSafe2(id, xmlString, record);
this.server.verify();
}
}
This is the test case what I'm trying to do but it still calling actual rest api

As pointed out by #JBNizet, you should take a look at MockRestServiceServer. It allows you to test Spring components which are using RestTemplate to make HTTP calls.
See MockRestServiceServer and #RestClientTest.

Related

how to skip the unkownhost exception in unit test when i use a dummyUrl with restTemplate exchange metho

this is my code for the method
public void sendFossInfoToMuninService(String productNumber, String rstate) throws Exception {
var product = new FossInfo();
product.setProductNumber(productNumber);
product.setRstate(rstate);
log.info(GENERIC_MSG_WHEN_SENDING, productNumber, rstate);
try{
RestTemplate restTemplate= new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> request = new HttpEntity<>(product.toString(), headers);
var response = restTemplate.exchange(muninHost+"/product", HttpMethod.POST, request, String.class);
if(response.getStatusCode().is2xxSuccessful()){
log.info(INFO_MSG_SUCCESS_MUNIN, productNumber,rstate);
}else{
log.error(ERR_MSG_FAILED_MUNIN_REGISTRATION, productNumber,rstate);
}
} catch (Exception e) {
log.info(ERR_MSG_UNSECCESSFUL_MUNIN_REGISTRATION, productNumber, rstate, e.getMessage());
}
}
and this is the unit test class
#RunWith(MockitoJUnitRunner.class)
#ExtendWith(OutputCaptureExtension.class)
#ExtendWith(MockitoExtension.class)
public class MuninServiceTest {
String dummyUrl = "http://muninService/url/to/upload/product";
#Mock
private RestTemplate restTemplate;
#InjectMocks
MuninService muninService;
#BeforeEach
void setUp(){
MockitoAnnotations.openMocks(this);
muninService = new MuninService(dummyUrl);
}
#SneakyThrows
#Test
void sendFossInfosToMuninSuccess(CapturedOutput output) {
String productNumber = "2/CXD32EJ";
String rState = "R12A";
var product = new FossInfo(productNumber,productNumber);
ResponseEntity<String> productEntity = new ResponseEntity<>(HttpStatus.ACCEPTED);
Mockito.when(restTemplate.exchange(
ArgumentMatchers.eq(dummyUrl),
ArgumentMatchers.eq(HttpMethod.POST),
ArgumentMatchers.<HttpEntity<String>>any(),
ArgumentMatchers.<ParameterizedTypeReference<String>>any())
).thenReturn(productEntity);
muninService.sendFossInfoToMuninService(productNumber,rState);
assertThat(output).contains("Successfully sending FOSS with product number " + productNumber + " and Rstate " + rState + " to Munin");
}
and this is the exception
error message I/O error on POST request for "http://muninService/url/to/upload/product/product": muninService; nested exception is java.net.UnknownHostException: muninService

How to correctly mock Interceptor pre handle method in spring boot integration tests

I'm writing an integration test to an API which receives #RequestAttribute List<String> permissions and HttpServletRequest request as method parameters. There is a custom interceptor which overrides the preHandle() method of HandlerInterceptor. This method receives HttpServletRequest request and HttpServletResponse response as parameters. Based on some logic there are some attributes set in HttpServletRequest request.
I'm writing an integration test where in I send an Http request to the endpoint of the application. I want to mock the interceptor and set these attributes myself.
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#RunWith(SpringRunner.class)
public class TradeChargesTest {
#LocalServerPort
public int port;
#MockBean
AppInterceptor interceptor;
// other stuff
#BeforeEach
void initTest() throws Exception {
// Want to write the mock interceptor logic here.
}
public TestRestTemplate restTemplate = new TestRestTemplate();
public String createURLWithPort(String uri) {
return "http://localhost:" + port + uri;
}
}
Test method:
public class UserInfoControllerTest extends TradeChargesTest {
TestRestTemplate restTemplate = new TestRestTemplate();
HttpHeaders headers = new HttpHeaders();
#Test
public void testUserInfoController(){
HttpEntity<String> entity = new HttpEntity<String>(null, headers);
ResponseEntity response = restTemplate.exchange(createURLWithPort("Testing/endpoint/user/v5/getUser?from_date=2022-04-01&page=1"), HttpMethod.GET, entity, Object.class);
assertEquals(200, response.getStatusCodeValue());
LinkedHashMap<String, Object> responseBodyMap = (LinkedHashMap<String, Object>) response.getBody();
assertEquals(3, responseBodyMap.get("totalHits"));
assertEquals(1, responseBodyMap.get("page"));
}
}

How to mock resttemplate calls in spring boot?

I tried to write test cases for the rest calls in my service which is calling the 3rd party api.
#RunWith(MockitoJUnitRunner.class)
public class ForceServiceTest {
private ForceService forceService;
#Mock
private ForceServiceConfig config;
#Mock
private RestTemplate restTemplate;
#Before
public void setup() {
forceService = new ForceService(config);
}
#Test
public void apiCall_valid() throws JSONException {
HttpHeaders headers = new HttpHeaders();
headers.set(CONTENT_TYPE, "application/x-www-form-urlencoded");
headers.set(ACCEPT, APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(
"id=null",
headers);
config.authTokenUrl = "https://ex...com/..";
Mockito.when(restTemplate.exchange(config.authTokenUrl, HttpMethod.POST, entity, Access.class)).thenReturn(null);
// Mockito.when(any()).thenReturn(null);
forceService.apiCall();
}
}
#Component
public class ForceService {
private ForceServiceConfig config;
private RestTemplate restTemplate = new RestTemplate();
public ForceService(ForceServiceConfig config) {
this.config = config;
}
private String apiCall() {
HttpHeaders headers = new HttpHeaders();
headers.set(CONTENT_TYPE, "application/x-www-form-urlencoded");
headers.set(ACCEPT, APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(
"&id=" + config.id,
headers);
ResponseEntity<Access> response = restTemplate.exchange(config.authTokenUrl, HttpMethod.POST, entity,
Access.class);
return response.getBody().token_type + " " + response.getBody().access_token;
}
}
I get the following error:
org.springframework.web.client.HttpClientErrorException: 404 Not Found
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:78)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:700)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:653)
It is calling the api in test class, which I do not want to happen.
I need to mock the resttemplate call of 3rd party api. How can I do it without being actually calling the api?
This is the problem
public class ForceService {
private ForceServiceConfig config;
private RestTemplate restTemplate = new RestTemplate(); // HERE
you are creating new REAL rest template. What you probably want is to
Use mock that you created in wrapping test class
Use real rest template and SPY on it.
A don't know is that your actualy structure (1 file 2 classes) but it is safe to assume it is not and in any case you can simply pass RestTemplate as ctor argument. So
#Component
public class ForceService {
private ForceServiceConfig config;
private RestTemplate restTemplate;
public ForceService(ForceServiceConfig config,RestTemplate restTemplate) {
this.restTemplate=restTemplate;
this.config = config;
}
and
#Before
public void setup() {
forceService = new ForceService(config,restTemplate);
}
Now if you want to RestTemplate to be just a stub that does literally nothing and return null on any calls if not instructed otherwiser - leave it as #Mock.
If you want however, to allow it to work normally and only intercept some specific method calls and stub responses, use spy.
#Mock
private RestTemplate restTemplate;
private RestTemplate restTemplate=Mockito.mock(RestTemplate.class)
or
private RestTemplate restTemplate=Mockito.spy(new RestTemplate());

RestTemplate mocking gives NullPointerException

My service class code is as below:
public class MyServiceImpl implements MegatillAccessService {
#Autowired
RestTemplate restTemplate;
#Value("${api.key}")
private String apiKey;
#Value("${customers.url}")
private String postUrl;
#Override
public String pushCustomerData(List<Customer> listOfcustomers, String storeId) throws MyServiceException {
Set<Customer> setOfCustomers = new HashSet<>(listOfcustomers);
int noOfCustomersLoadedSuccessfully =0;
MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>();
headers.add("apiKey", apiKey);
headers.add("Content-Type", "application/json");
headers.add("storeId", storeId);
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
for(Customer customer: setOfCustomers){
HttpEntity<Customer> request = new HttpEntity<Customer>(customer, headers);
CustomerDataDto customerDataDto = null;
try {
customerDataDto = restTemplate.exchange(postUrl, HttpMethod.POST, request, CustomerDataDto.class).getBody();
}
catch (HttpClientErrorException ex) {
if (ex.getStatusCode().equals(HttpStatus.NOT_FOUND)) {
log.error("The customers service is not available to load data: "+ ex.getResponseBodyAsString(), ex);
throw new MyServiceException("The customers service is not available to load data",new RuntimeException(ex));
}
else{
log.warn("Error for customer with alias: "+customer.getAlias() +" with message: "+ ex.getResponseBodyAsString(), ex);
if(!ex.getResponseBodyAsString().contains("already found for this shop")){
throw new MyServiceException("An error occurred while calling the customers service with status code "+ex.getStatusCode(),new RuntimeException(ex));
}
}
}
catch(Exception e){
throw new MyServiceException("An error occurred while calling the customers service: ",new RuntimeException(e));
}
if(null != customerDataDto) {
noOfCustomersLoadedSuccessfully++;
log.debug("--------Data posted successfully for: ---------"+customerDataDto.getAlias());
}
}
String messageToReturn = "No. of unique customers from source: "+setOfCustomers.size()+". No. of customers loaded to destination without error: "+noOfCustomersLoadedSuccessfully;
return messageToReturn;
}
}
My Test class is as below:
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
public class MyServiceTest {
#InjectMocks
private MyService myService = new MyServiceImpl();
#Mock
RestTemplate restTemplate;
#Before
public void setUp() throws Exception
{
MockitoAnnotations.initMocks(this);
initliaizeModel();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
}
#Test
public void pushAllRecords(){
Mockito.when(restTemplate.exchange(Matchers.anyString(), Matchers.any(HttpMethod.class), Matchers.<HttpEntity<?>> any(), Matchers.<Class<CustomerDataDto>> any()).getBody()).thenReturn(customerDataDto);
/*Mockito.when(restTemplate.exchange(Mockito.anyString(),
Mockito.<HttpMethod> eq(HttpMethod.POST),
Matchers.<HttpEntity<?>> any(),
Mockito.<Class<CustomerDataDto>> any()).getBody()).thenReturn(customerDataDto);*/
String resultReturned = myService.pushCustomerData(customers,"1235");
assertEquals(resultReturned, "No. of unique customers from source: 2. No. of customers loaded to destination without error: 2");
}
}
While running the test, I am getting NullPointerException at the line where I am giving the Mockito.when and thenReturn condition. I tried many combinations but it is still giving NPE. I can't even reach the method invocation.Can you please let me know where am I going wrong?
You get NullPointerException because you are doing too much in your Mockito.when. Your code inside when (shorter version):
restTemplate.exchange(args).getBody()
You are trying to mock getBody() but it is called on exchange(args). And what does exchange(args) returns? Mockito doesn't know what it should return and you didn't specify that so by default it returns null.
That's why you get NPE.
To fix this you can either do mocking step by step, ie.
ResponseEntity re = Mockito.when(exchange.getBody()).thenReturn(customerDataDto);
Mockito.when(restTemplate.exchange()).thenReturn(re);
or specify mock to return deep stubs, like that (if you want to use annotations):
#Mock(answer = Answers.RETURNS_DEEP_STUBS)
RestTemplate restTemplate;

How to Mock client side rest service

I'm trying to create mockito test run for rest api below is the controller class followed by mock test which I'm trying to execute but the problem is it is still calling actual rest api instead of mocking it,
1) Controller Class
public void sendData(ID id, String xmlString, Records record) throws ValidationException{
ClientHttpRequestFactory requestFactory = new
HttpComponentsClientHttpRequestFactory(HttpClients.createDefault());
RestTemplate restTemplate = new RestTemplate(requestFactory);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
restTemplate.setMessageConverters(messageConverters);
MultiValueMap<String,String> header = new LinkedMultiValueMap<>();
header.add("x-api-key",api_key);
header.add("Content-Type",content_type);
header.add("Cache-Control",cache_control);
HttpEntity<String> request = new HttpEntity<>(xmlString, header);
try {
restTemplate.postForEntity(getUri(id,record), request, String.class);
}catch (RestClientResponseException e){
throw new ValidationException("Error occurred while sending a file to some server "+e.getResponseBodyAsString());
}
}
2) Test class
#RunWith(MockitoJUnitRunner.class)
public class Safe2RestControllerTest {
private MockRestServiceServer server;
private RestTemplate restTemplate;
private restControllerClass serviceToTest;
#Before
public void init(){
//some code for initialization of the parameters used in controller class
this.server = MockRestServiceServer.bindTo(this.restTemplate).ignoreExpectOrder(true).build();
}
#Test
public void testSendDataToSafe2() throws ValidationException, URISyntaxException {
//some code here when().then()
String responseBody = "{\n" +
" \"responseMessage\": \"Validation succeeded, message
accepted.\",\n" +
" \"responseCode\": \"SUCCESS\",\n" +
" 2\"responseID\": \"627ccf4dcc1a413588e5e2bae7f47e9c::0d86869e-663a-41f0-9f4c-4c7e0b278905\"\n" +
"}";
this.server.expect(MockRestRequestMatchers.requestTo(uri))
.andRespond(MockRestResponseCreators.withSuccess(responseBody,
MediaType.APPLICATION_JSON));
serviceToTest.sendData(id, xmlString, record);
this.server.verify();
}
}
How should I go ahead, any suggestion would be appreciated.
Spring's MVC test apparatus makes this quite easy.
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = YourController.class)
public class YourControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void testSendDataToSafe2() throws Exception {
// prepare e.g. create the requestBody
MvcResult mvcResult = mockMvc.perform(post(uri).contentType(MediaType.APPLICATION_JSON).content(requestBody))
.andExpect(status().isOk())
.andReturn();
Assert.assertEquals(responseBody, mvcResult.getResponse().getContentAsString());
}
}
For more details, see the section titled "Add Unit Tests" here and/or the section titled "Auto-configured Spring MVC tests" here.
Your question also states "the problem is it is still calling actual rest api" so I'm guessing that, in addition to calling your controller is a test context, you want that mock out some of the behaviour of that controller. Specifically, you want to mock the RestTemplate instance used in that controller. If so, then would have to change the controller implementation such that the RestTemplate instance is an #Autowired class member. Then you would declare a mock for that in your test case like so:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = YourController.class)
public class YourControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private RestTemplate restTemplate;
#Test
public void testSendDataToSafe2() throws Exception {
// prepare e.g. create the requestBody
// tell your mocked RestTemplate what to do when it is invoked within the controller
Mockito.when(restTemplate.postForEntity(..., ..., ...)).thenReturn(...);
MvcResult mvcResult = mockMvc.perform(post(uri).contentType(MediaType.APPLICATION_JSON).content(requestBody))
.andExpect(status().isOk())
.andReturn();
Assert.assertEquals(responseBody, mvcResult.getResponse().getContentAsString());
}
}
The above code is valid for spring-test:4.3.10.RELEASE.

Resources