How mock REST Request - java

I am using Mockito in JUnit and I have a method making a request to a microservice using RestTemplate.
private static final String REQUESTOR_API_HOST = "http://localhost:8090/requestor/v1/requestors/";
public TokenRequestorPayload getTokenRequestor(Long id) {
restClient = new RestTemplate();
return restClient.getForObject(REQUESTOR_API_HOST + id, TokenRequestorPayload.class);
}
This method returns a JSON object which will be deserialize in TokenRequestorPayload class.
When I execute unit tests they fail because mock didn't work and I got a org.springframework.web.client.ResourceAccessException. How can I mock my RestTemplate?
Test
RestTemplate restTemplate = Mockito.spy(RestTemplate.class);
Mockito.doReturn(this.tokenRequestorMockJson()).when(restTemplate).getForObject(Mockito.anyString(), Mockito.eq(TokenRequestorPayload.class));
Error
org.springframework.web.client.ResourceAccessException: I/O error on
GET request for "http://localhost:8090/requestor/v1/requestors/1":
Connection refused (Connection refused); nested exception is
java.net.ConnectException: Connection refused (Connection refused)

In your test you are defining behavior for a mock instance of RestTemplate, which is good.
RestTemplate restTemplate = Mockito.spy(RestTemplate.class);
However, the same instance is not used by the Class Under Test which creates a new RestTemplate instance each time and uses that one.
public TokenRequestorPayload getTokenRequestor(Long id, RestTemplate restClient) {
restClient = new RestTemplate(); // it uses this instance, not the mock
return restClient.getForObject(REQUESTOR_API_HOST + id, TokenRequestorPayload.class);
}
So you need to find a way to bring the mock instance to the getTokenRequestor() method.
E.g. this can be accomplished by turning restClient into a method parameter:
public TokenRequestorPayload getTokenRequestor(Long id, RestTemplate restClient) {
return restClient.getForObject(REQUESTOR_API_HOST + id, TokenRequestorPayload.class);
}
and the test might look something like like:
#Test
public void test() {
RestTemplate restTemplateMock = Mockito.spy(RestTemplate.class);
Mockito.doReturn(null).when(restTemplateMock).getForObject(Mockito.anyString(), Mockito.eq(TokenRequestorPayload.class));
// more code
instance.getTokenRequestor(id, restTemplateMock); // passing in the mock
}

Use Spring's mocking support for RestTemplate rather than trying to mock RestTemplate - much nicer:
Create a mock rest service server binding it to your RestTemplate:
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
Record a call to that mock server e.g.
mockServer.expect(requestTo("some url").andExpect(method(HttpMethod.POST))
.andRespond(withSuccess("addSuccessResult", MediaType.TEXT_PLAIN));
Then verify the mocks have been called:
mockServer.verify();
See https://objectpartners.com/2013/01/09/rest-client-testing-with-mockrestserviceserver/

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.

Is there any way to manually programatically create HttpSession in the server without neccessety of sending request?

I am coding on java using the Spring boot application.
I have the following method in Controller:
getCustomerById();
and two Filters for the request that executed in the following order:
CheckSessionFilter that checks whether a session exists for the request
CheckCustomerStatusFilter that check internal status for customer
I need to implement an integration test for the getCustomerById() that checks the behavior when a request comes to CheckCustomerStatusFilter. For that I need to send a request with the valid HttpSession in cookies; But how to get this active session without doing additional requests from my application responsible for the session creation. Is there any way to manually create the HttpSession using Servlet API and then just send request to the test server with the SESSION in the cookie?
I do my integration test by fully launching Spring context and sending request to endpoint handled by getCustomerById() method:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = Runner.class, webEnvironment =
SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles("test")
public class CustomerFiltersTest {
#Autowired
TestRestTemplate testRestTemplate;
#SpyBean
CheckSessionFilter checkSessionFilter;
#SpyBean
CheckCustomerStatusFilter checkCustomerStatusFilter;
#Test
public void requestToGetCustomerByIdEndpoint_shouldGoThroughCheckCustomerStatusFilter() throws Exception {
final String host = "http://localhost:" + port;
HttpEntity<?> entity = new HttpEntity(null, null);
String requestUrl = host + CUSTOMER_BY_ID_URL;
testRestTemplate.exchange(
requestUrl,
HttpMethod.GET, entity, String.class);
verify(checkSessionFilter, times(1)).doFilter(any(), any(), any());
verify(checkCustomerStatusFilter, times(1)).doFilter(any(), any(), any());
}
Try mocking the HttpSession Object.

Why mockServer.verify is needed in MockRestServiceServer

When using MockRestServiceServer with andExpect to test
mockServer.expect(requestTo("/hotels/42")).andExpect(method(HttpMethod.GET))
.andRespond(withSuccess("{ \"id\" : \"42\", \"name\" : \"Holiday Inn\"}", MediaType.APPLICATION_JSON));
Then test failed if found unexpected behavior,
For example no further requests expected: HTTP if sent to unexpected URL
My Config:
#SpringBootApplication(scanBasePackages = { "..." })
public class MyConfig extends SpringBootServletInitializer {
My Test class
#ContextConfiguration( classes = {MyConfig.class})
#ActiveProfiles("local")
#WebAppConfiguration
public class My Test extends AbstractTestNGSpringContextTests {
#Autowired
#InjectMocks
private ServiceUnderMock serviceUnderMock;
private AutoCloseable closeable;
#BeforeClass
public void initMocks() {
closeable = MockitoAnnotations.openMocks(this);
mockServer = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();
}
#AfterClass
public void releaseMocks() throws Exception {
closeable.close();
}
#Autowired
private RestTemplate restTemplate;
private MockRestServiceServer mockServer;
#Test
public void test() {
try {
mockServer.expect(ExpectedCount.min(1),
requestTo(new URI("https://www.google.com")))
.andExpect(method(HttpMethod.GET));
} catch (URISyntaxException e) {
Assert.fail("failed to create mock");
}
serviceUnderMock.doSomething();
}
So why we need to add the mockServer.verify()?
At the end of the test use verify() to ensure all expected requests were actually performed.
The idea of MockRestServiceServer is that it allows you mock the external server such that the RestTemplate does not really need to send the requests to the actual server during the testing. Instead it just sends the requests to this MockRestServiceServer (think that it is a kind of in-memory server) and it will return the configured mocked responses for the corresponding requests.
You have to configure all the expected requests that the MockRestServiceServer will received and its corresponding responds before the test.
So basically there are two things needed to be verified which are :
For every request sent by RestTemplate , there should be a mocked response configured for that request in the MockRestServiceServer
For all the requests that are to be expected to be received on the MockRestServiceServer , the RestTemplate should really send out all of these expected requests.
(1) will be verified automatically whenever the RestTemplate send out a request. The exception no further requests expected: HTTP that you mentioned is because it fails (1) (i.e. forget to stub this request in the MockRestServiceServer)
(2) will not be verified automatically . You have to call MockRestServiceServer.verify() manually in order to verify it.
An example :
mockServer.expect(requestTo(new URI("https://www.yahoo.com")))
.andExpect(method(HttpMethod.GET)).andRespond(withSuccess());
mockServer.expect(requestTo(new URI("https://www.google.com")))
.andExpect(method(HttpMethod.GET)).andRespond(withSuccess());
mockServer.expect(requestTo(new URI("https://www.stackoverflow.com")))
.andExpect(method(HttpMethod.GET)).andRespond(withSuccess());
restTemplate.getForEntity("https://www.yahoo.com", String.class);
restTemplate.getForEntity("https://www.google.com", String.class);
Without mockServer.verify() , the test still passes although RestTemplate does not send the request to https://www.stackoverflow.com which the MockServer is expected to be received.
But with mockServer.verify() , it can check that and hence fails the test.

Getting Connection refused: connect; nested exception is java.net.ConnectException: with MockRestServiceServer

I want to mock RestTemplate object which is calling another microservice and receiving configuration for a particular device:
ResponseEntity<ConfigurationResponse> configurationResponse = restTemplate
.getForEntity("http://localhost:8081/configuration/serial/" + dto.getSerNum(), ConfigurationResponse.class);
I am mocking above RestTemplate like:
mockServer.expect(requestTo(
"http://localhost:8081/configuration/serial/" + device.getSerNum()))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess(toJson(
ConfigurationResponse.builder()
.ip("192.168.1.1")
.netMask("255.255.0.0")
.build()),
MediaType.APPLICATION_JSON_UTF8));
but after the test start I am receiving an exception:
org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8081/configuration/serial/XXX-BBB-KKK": Connection refused: connect; nested exception is java.net.ConnectException: Connection refused: connect
I am instantiating RestTemplate and MockRestServiceServer like:
class DeviceServiceTest {
private DeviceService deviceService;
private DeviceRepository deviceRepository = mock(DeviceRepository.class);
private RestTemplate restTemplate = new RestTemplate();
private MockRestServiceServermockServer = MockRestServiceServer.createServer(restTemplate);
#BeforeEach
void setUp() {
deviceService = new DeviceService(deviceRepository);
mockServer = MockRestServiceServer.createServer(restTemplate);
restTemplate = new RestTemplate();
}
}
I used below example from link below
how-mock-rest-request
but it did not bring a desirable effect.
I will be grateful for a piece of advice on how to fix my mock to establish a connection in my test.
EDIT
Basing on the topic click I know that I suppose to have the same bean of RestTemplate in service and in the test but honestly I don't know how to make it happened. I am instantiating a RestTemplate object in a method which I am testing.

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