7

I am trying to test my codebase in the event OkHttpClient throws an IOException

Code under test

    try (var response = okHttpClient.newCall(request).execute()) {
        return response;
    } catch (final IOException e) {
        log.error("IO Error from API", e);
        throw new ApiException(e.getMessage(), e);
    }

Test

@Test
void createCustomer_WhenValidRequestAndIOException_ThenThrowAPIException() throws ZeusServiceException, ZeusClientException {

    //Given
    final OkHttpClient okHttpClientMock = mock(OkHttpClient.class, RETURNS_DEEP_STUBS);
    final OkHttpClient.Builder okHttpBuilderMock = mock(OkHttpClient.Builder.class);
    httpClient = new HttpClient(okHttpClientMock, configuration, objectMapper);

    //When
    when(okHttpClientMock.newBuilder()).thenReturn(okHttpBuilderMock);
    when(okHttpBuilderMock.build()).thenReturn(okHttpClientMock);
    when(okHttpClientMock.newCall(any())).thenThrow(IOException.class);
    final var result = httpClient.createCustomer(request);

    assertThatThrownBy(() -> httpClient.createCustomer(request))
        .isInstanceOf(ApiException.class)
        .hasMessage("IO Error from API");
}

I tried to mock the OkHttpClient and Builder class, however Builder is final, and mockito cannot mock it.

In the constructor of the class under test, you are recommended to create a new instance of OkHttpClient by invoking this

        this.okHttpClient = okHttpClient.newBuilder().build();

I tried to create a wrapper around OkHttpClient but that didn't work either

    public class OkHttpClientWrapper extends OkHttpClient {

        private OkHttpClient okHttpClient;

        public OkHttpClientWrapper(final OkHttpClient okHttpClient) {
            this.okHttpClient = okHttpClient;
        }

        @Override
        public OkHttpClient.Builder newBuilder() {
            return new Builder(okHttpClient);
        }
    }
}

How can I force okhttpclient to throw an IOException?

tomaytotomato
  • 3,788
  • 16
  • 64
  • 119
  • Have you looked into PowerMockito to mock final classes? – Vince Aug 23 '21 at 16:37
  • Yea but people say PowerMockito is evil https://stackoverflow.com/questions/30162200/why-not-powermock – tomaytotomato Aug 23 '21 at 16:38
  • maybe creating a `Factory` for `OkHttpClient` and then mocking that `Factory`? – Alberto Sinigaglia Aug 23 '21 at 16:38
  • Have you considered not mocking it at all, but looking up how OkHttp recommends you test this case? – Louis Wasserman Aug 23 '21 at 16:42
  • @tomaytotomato Did you read the answers behind it? It suggests a design issue. However, these are not your types. You can't redesign those types to be more easily tested in a natural manner. There could be other ways to handle the situation (as Louis recommends), but rejecting PowerMockito just because you read on the internet that it's evil, without diving into _why_ it's evil, may lead you to writing more complex test code than actually needed. – Vince Aug 23 '21 at 16:50
  • 1
    Doesn't change the fact though that PowerMock *is* evil, and that the 2021 answer to unit testing restful endpoints is rather to use something like https://github.com/square/okhttp/tree/master/mockwebserver instead. – GhostCat Aug 24 '21 at 09:59
  • 1
    @GhostCat indeed there is a way in MockWebServer to simulate a closed/terminated connection, which covers the test scenario. Please see submitted answer – tomaytotomato Aug 24 '21 at 16:33

2 Answers2

7

In the end the best solution was to leverage OkHttp's MockWebServer

https://github.com/square/okhttp/tree/master/mockwebserver

Using a scenario where MockWebserver unexpectedly terminates the HTTP connection, causes an IOException scenario

@Test
void createCustomer_WhenValidRequestAndServerTerminatesConnection_ThenThrowIOException() throws ZeusServiceException, ZeusClientException {

    //Given
    final CreateCustomerRequest request = CreateCustomerRequest.builder().build();

    //When
    mockWebServer.enqueue(new MockResponse()
        .setBody(new Buffer().write(new byte[4096]))
        .setSocketPolicy(SocketPolicy.DISCONNECT_DURING_RESPONSE_BODY));

    assertThatThrownBy(() -> httpClient.createCustomer(request))
        .isInstanceOf(IOException.class)
        .hasMessage("unexpected end of stream");
}

Note: use of AssertJ library in this test

tomaytotomato
  • 3,788
  • 16
  • 64
  • 119
1

You have a few options:

Wojtek
  • 1,410
  • 2
  • 16
  • 31