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.
Related
I'm new to TestRestTemplate and with in Spring framework in general, and I'm trying to verify if a ResponseStatusException is thrown by my controller. For example the following degenerated request:
#RestController
public class UserManagementController {
#RequestMapping(value = "/users/{id}", method = RequestMethod.PUT)
public ResponseEntity<UserDTO> updateUser(#RequestBody UserDTO userDTO, #PathVariable("id") String id){
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "user not found");
}
}
And in my test I'm using TestRestTemplate:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserManagementComponentTest {
#Autowired
private TestRestTemplate testRestTemplate;
#Test
public void testUpdateStaleUser() {
UserDTO updateUserDTORequest = UserDTO.builder();
assertThrows(ResponseStatusException.class,
() -> testRestTemplate.exchange("/users/" + createdUserId,
HttpMethod.PUT, new HttpEntity<>(updateUserDTORequest), UserDTO.class));
}
}
I expect to get ResponseStatusException, but the tests fails with the following message:
org.opentest4j.AssertionFailedError: Unexpected exception type thrown ==> expected: <org.springframework.web.server.ResponseStatusException> but was: <org.springframework.web.client.RestClientException>
I don't understand why RestClientException is thrown.
ResponseStatusException is the exception thrown in the server side and it will be handled by the spring-mvc framework in the server internally to return the suitable HTTP error response.
While TestRestTemplate just like a client-side REST library and hence it never can catch and handle the exception that is thrown internally from the API server.
TestRestTemplate can only throw its own exception when handling the HTTP response returned from calling an API. It will delegate to its internal RestTemplate 's ResponseErrorHandler for handling the error HTTP response.
So RestClientException is thrown by the ResponseErrorHandler that you configured for the TestRestTemplate.
Actually by default , the TestRestTemplate is configured to be fault tolerant such that it behaves in a test-friendly way by not throwing exceptions such that you can asserting directly on the returned HTTP status code or payload (see this) :
ResponseEntity<String> response = testRestTemplate.exchange("/users/" + createdUserId, HttpMethod.PUT, new HttpEntity<>(updateUserDTORequest), UserDTO.class));
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
assertThat(response.getBody()).isEqualTo(xxxxx);
I am working on implementing WebSocket integration tests. I cannot get the mock user to be passed to the client.
#WithMockUser(value = "user", roles = "USER")
void test() throws ExecutionException, InterruptedException, TimeoutException {
webSocketStompClient = new WebSocketStompClient(
new SockJsClient(List.of(new WebSocketTransport(new StandardWebSocketClient())))
);
StompSession session = webSocketStompClient
.connect(String.format("ws://localhost:%d/ws", port),
new StompSessionHandlerAdapter() {
}
)
.get(1, SECONDS);
}
Unfortunately, I receive the following error:
TopicInterceptorIT > test() FAILED
java.util.concurrent.ExecutionException at TopicInterceptorIT.java:57
Caused by: org.springframework.messaging.simp.stomp.ConnectionLostException at DefaultStompSession.java:518
2022-01-17 12:04:19.415 WARN [,,,] 44518 --- [ XNIO-1 I/O-9] w.s.h.ExceptionWebSocketHandlerDecorator : Unhandled exception after connection closed for ExceptionWebSocketHandlerDecorator [delegate=LoggingWebSocketHandlerDecorator [delegate=WebSocketMsgEnhancer [delegate=SubProtocolWebSocketHandler[StompSubProtocolHandler[v10.stomp, v11.stomp, v12.stomp]]]]]
org.springframework.messaging.MessageDeliveryException: Failed to send message to ExecutorSubscribableChannel[clientInboundChannel]; nested exception is org.springframework.security.access.AccessDeniedException: Access is denied
Do I have to manually create the access token and inject it into the request headers? Or is there an approach similar to the #WithMockUser over MockMvc familiar when testing REST endpoints?
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.
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.
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/