How to test a RestClientException with MockRestServiceServer
Asked Answered
S

5

17

While testing a RestClient-Implementation I want to simulate a RestClientException that may be thrown by some RestTemplate-methods in that implementation f.e. the delete-method:

@Override
public ResponseEntity<MyResponseModel> documentDelete(String id) {
    template.setErrorHandler(new MyResponseErrorHandler());
    ResponseEntity<MyResponseModel> response = null;
    try {
        String url = baseUrl + "/document/id/{id}";
        response = template.exchange(url, DELETE, null, MyResponseModel.class, id);
    } catch (RestClientException ex) {
        return handleException(ex);
    }
    return response;
}

How can I achieve this?

I define the mock-server in this way:

@Before
public void setUp() {
    mockServer = MockRestServiceServer.createServer(template);
    client = new MyRestClient(template, serverUrl + ":" + serverPort);
}
Shuttle answered 3/3, 2017 at 11:13 Comment(0)
H
12

You can take advantage of the MockRestResponseCreators for mocking 4xx or 5xx responses from the mockRestServiceServer.

For example for testing a 5xx - Internal server error:

mockServer.expect(requestTo("your.url"))
                .andExpect(method(HttpMethod.GET/POST....))
                .andRespond(withServerError()...);

In your case the RestClientException is thrown for client-side HTTP errors, so the example above can be fine tuned for a 4xx exception by using: ...andRespond(withBadRequest()); or ...andRespond(withStatus(HttpStatus.NOT_FOUND));

For a more simpler usage of these methods you use static imports for org.springframework.test.web.client.MockRestServiceServer,org.springframework.test.web.client.response.MockRestResponseCreators

Hornback answered 3/3, 2017 at 11:21 Comment(5)
hm ... ok I could use the builder-methods to create finally a ClientHttpResponse but how can I fine-tune such a response via the builder-methods so that an exception is thrown/simulated?Shuttle
Adding andRespond(withStatus(HttpStatus.NOT_FOUND)); ahiuld help youHornback
Adding andRespond(withStatus(HttpStatus.NOT_FOUND)) delivers a ClientHTTPResponse with the defined http-status-code. that means, a call like response = template.exchange(url, DELETE, null, MyResponseModel.class, id); brings back a response and not a RestClientExceptionShuttle
The restTemplate is bound to your mockServer. By looking at the source of the restTemplate you can see that when an 4xx code is encountered in the response a specific RestClientException is thrown. So mocking an 4xx response from the server should provide get you in the catch block.Hornback
Hi Alex, I think you're right. I forgot, that I set a custom errorHandler and it catched me this exception away, when the status-code is 4xx or 5xx. So a RestClientException at this place can't be occur. :-) Thanks for your advices!Shuttle
H
17

You can test throwing runtime exceptions from the MockRestServiceServer, although this class, as of Spring 5.0.0.RC4, is not designed for it (which means it may not work for more complex use cases):

RestTemplate yourApi;
MockRestServiceServer server = MockRestServiceServer.createServer(yourApi);

server.expect(requestTo("http://..."))
    .andRespond((response) -> { throw new ResourceAccessException(
        new ConnectException("Connection reset")); });

It seems to work in tests:

  • where there's only one RestTemplate call,
  • where the exception is thrown as a result of the last expectation.

I wasn't able to expect two consecutive exceptions; the MockRestSeriviceServer (more concrete, the SimpleRequestExpectationManager) throws IllegalStateException on replaying the second expectation.

Hindsight answered 11/12, 2017 at 9:50 Comment(1)
This does not even compileRegolith
H
12

You can take advantage of the MockRestResponseCreators for mocking 4xx or 5xx responses from the mockRestServiceServer.

For example for testing a 5xx - Internal server error:

mockServer.expect(requestTo("your.url"))
                .andExpect(method(HttpMethod.GET/POST....))
                .andRespond(withServerError()...);

In your case the RestClientException is thrown for client-side HTTP errors, so the example above can be fine tuned for a 4xx exception by using: ...andRespond(withBadRequest()); or ...andRespond(withStatus(HttpStatus.NOT_FOUND));

For a more simpler usage of these methods you use static imports for org.springframework.test.web.client.MockRestServiceServer,org.springframework.test.web.client.response.MockRestResponseCreators

Hornback answered 3/3, 2017 at 11:21 Comment(5)
hm ... ok I could use the builder-methods to create finally a ClientHttpResponse but how can I fine-tune such a response via the builder-methods so that an exception is thrown/simulated?Shuttle
Adding andRespond(withStatus(HttpStatus.NOT_FOUND)); ahiuld help youHornback
Adding andRespond(withStatus(HttpStatus.NOT_FOUND)) delivers a ClientHTTPResponse with the defined http-status-code. that means, a call like response = template.exchange(url, DELETE, null, MyResponseModel.class, id); brings back a response and not a RestClientExceptionShuttle
The restTemplate is bound to your mockServer. By looking at the source of the restTemplate you can see that when an 4xx code is encountered in the response a specific RestClientException is thrown. So mocking an 4xx response from the server should provide get you in the catch block.Hornback
Hi Alex, I think you're right. I forgot, that I set a custom errorHandler and it catched me this exception away, when the status-code is 4xx or 5xx. So a RestClientException at this place can't be occur. :-) Thanks for your advices!Shuttle
A
7

Answer by Alex Ciocan works for different http status responses, so if you want those, go with that as that's the cleanest way to go. I had a problem that I needed to be able to test also for connection reset and other network-level problems, which are trickier to simulate.

Answer by MaDa works for some use cases, but it didn't work for me when using AsyncRestTemplate, as it throws too early. However it did lead me to right direction. This one seems to work with async calls as well:

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

// ...

ClientHttpResponse exceptionThrowingResponse = mock(ClientHttpResponse.class);

when(exceptionThrowingResponse.getStatusCode()) // getRawStatusCode() in latest spring
    .thenThrow(new IOException("connection reset"));

mockServer.expect(requestTo("http://localhost:123/callme"))
    .andRespond((response) -> exceptionThrowingResponse);

This seems to also work for consecutive exceptions, as well as different http statuses.

Aerobic answered 6/6, 2018 at 9:22 Comment(0)
I
0

You can simulate it with MockRestResponseCreators.withException(SocketTimeoutException("any")

import org.springframework.boot.test.autoconfigure.web.client.RestClientTest
import org.springframework.test.web.client.MockRestServiceServer
import org.springframework.test.web.client.match.MockRestRequestMatchers
import org.springframework.test.web.client.response.MockRestResponseCreators
import java.net.SocketTimeoutException

@RestClientTest
class MockTest {

    @Autowired lateinit var server: MockRestServiceServer

    @Autowired lateinit var sut: MySystemUnderTest

    @Test
    fun `it should handle network error`() {
        server
            .expect(MockRestRequestMatchers.anything())
            .andRespond(MockRestResponseCreators.withException(SocketTimeoutException("test")))

        assertThrows<TheExpectedException> {
            sut.makeRequest()
        }
    }
}
Integrand answered 19/3 at 9:42 Comment(0)
M
-1

How about this :

@Spy
@InjectMocks
ClasstoMock objToMock;

@Test
public void testRestClientException() throws  Exception {       
        try {
        Mockito.when(this.objToMock.perform()).thenThrow(new RestClientException("Rest Client Exception"));
        this.objToMock.perform();
        }
        catch(Exception e){
            Assert.assertEquals(RestClientException.class, e.getClass());
        }  
Mend answered 11/9, 2019 at 20:56 Comment(1)
Long story short, this answer is not useful until ClasstoMock is unveiledPangenesis

© 2022 - 2024 — McMap. All rights reserved.