18

I have the below method which executes a GET request 3 times until its successfully.

What would be the better way to mock this method? I want to mock and test status_code 401, status code 500 and want to test if the method is executed thrice.

In python, we have https://github.com/getsentry/responses which mocks the requests directly, so its easy to test these methods.

Is there anything equivalent in Java.

@Override
    public <T> UResponse<T> get(Request request, JSONUnmarshaler<T> unmarshaller, Gson gson) throws UException {
        int status_code = 0;
        String next = null;
        String rawJSON = null;
        JsonElement jsonelement = null;
        Boolean retry = true;
        try {
            int attempts = 3;
            while ((attempts >= 0)  && (retry) && status_code != 200){
                Response response = this.client.newCall(request).execute();

                rawJSON = response.body().string();

                jsonelement = gson.fromJson(rawJSON, JsonElement.class);

                next = gson.fromJson(jsonelement.getAsJsonObject().get("next"), String.class);

                status_code = response.code();

                if (status_code == 401) {
                    try {
                        logger.warn("token expired");
                        TimeUnit.SECONDS.sleep(5);
                        retry = true;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                if ((status_code / 100 ) == 5){
                    logger.warn("gateway error");
                    retry = true;                   
                }
                attempts -= 1;
                retry = false;

            }
            if (status_code != 200){
                throw new UException();
            }
            return new UResponse<T>(status_code, next, rawJSON,
                    unmarshaller.fromJSON(gson, jsonelement.getAsJsonObject()));

        } catch (IOException e) {
            e.printStackTrace();
            throw new UException();

        }
user1050619
  • 19,822
  • 85
  • 237
  • 413
  • You can mock (with mockito, jmockit, etc) the `client.newCall(request).execute()` calls to return an expected response and check for the number of invocations, or maybe the simpler way, use the [okhttp-client-mock](https://github.com/gmazzo/okhttp-client-mock) (MIT), setup your rule for [3 times](https://github.com/gmazzo/okhttp-client-mock/blob/2fccb3d93f8a812ff779862eeff3fa2e03b75708/src/main/java/okhttp3/mock/Rule.java#L263), and check that it was [consumed](https://github.com/gmazzo/okhttp-client-mock/blob/2fccb3d93f8a812ff779862eeff3fa2e03b75708/src/main/java/okhttp3/mock/Rule.java#L91) – Morfic Jan 31 '19 at 14:15
  • Or potentially use a [mock-server](https://github.com/square/okhttp/tree/master/mockwebserver) – Morfic Jan 31 '19 at 14:39
  • @Morfic thanks, this was really helpful.. – user1050619 Jan 31 '19 at 17:47
  • 2
    Np, feel free to post your answer once you get it working so others can benefit from it as well. Cheers – Morfic Jan 31 '19 at 17:53

3 Answers3

28

This would be hard to test because it loops. One solution would be to break out this.client.newCall(request).execute() into a separate function sendRequest(Request request, int attemptsRemaining). Then you could use a spy that stubs that method to return different responses depending on the number of attempts remaining.
The nice thing I realized about okhttp is that you can just build a fake response yourself. For example,

Request mockRequest = new Request.Builder()
                .url("https://some-url.com")
                .build();
return new Response.Builder()
                .request(mockRequest)
                .protocol(Protocol.HTTP_2)
                .code(401) // status code
                .message("")
                .body(ResponseBody.create(
                        MediaType.get("application/json; charset=utf-8"),
                        "{}"
                ))
                .build();

So you can create and stub a simple method that returns a response, and you're basically good to go for testing.

Andrew Puglionesi
  • 945
  • 1
  • 11
  • 16
24

There are mocking frameworks you can add to your list of dependencies, and that's all fine & dandy but, I'm old-fashioned and prefer just mocking the remoteCall and execute methods:

copy this method into your test class:

private static OkHttpClient mockHttpClient(final String serializedBody) throws IOException {
        final OkHttpClient okHttpClient = mock(OkHttpClient.class);

        final Call remoteCall = mock(Call.class);

        final Response response = new Response.Builder()
                .request(new Request.Builder().url("http://url.com").build())
                .protocol(Protocol.HTTP_1_1)
                .code(200).message("").body(
                   ResponseBody.create(
                        MediaType.parse("application/json"),
                        serializedBody
                ))
                .build();

        when(remoteCall.execute()).thenReturn(response);
        when(okHttpClient.newCall(any())).thenReturn(remoteCall);

        return okHttpClient;
    }

usage:

final OkHttpClient mockedClient = mockHttpClient("{\"key\": \"val\"}");
cosbor11
  • 14,709
  • 10
  • 54
  • 69
  • 1
    To fix deprecated use of ResponseBody.create() : ResponseBody.Companion.create( serializedBody, MediaType.parse("application/json") )) – vhamon Nov 10 '22 at 09:26
  • This is great, thank you! But now the arguments to `ResponseBody.create()` need to swap order—the original order provided `(mediaType, serializedBody)` is deprecated and now must be `(serializedBody, mediaType)` Ref: https://github.com/square/okhttp/issues/6655#issuecomment-828355734 – Tina Feb 02 '23 at 01:46
-2
@Test
fun verifyFailureSingleEventListener() = runBlocking {
    val sender = createSender(webServer.url("/").toString())

    webServer.enqueue(MockResponse().setResponseCode(400))
    val testEvent1 = mutableMapOf("Click" to "Open")
    sender.sendSingleEvent(testEvent1)

    assertEquals("Failure", eventListener.getStatus())
    eventListener.resetStatus()
}

we can set mockresponse to have 400 status code or other status code to mock response

chia yongkang
  • 786
  • 10
  • 20
  • This is very unclear. What does `createSender(String)` do and its return type? Same goes for `evenListener`, not defined anywhere. – Chisko Apr 28 '22 at 20:02