How to create a retrofit.Response object during Unit Testing with Retrofit 2

AndroidUnit TestingMockitoRetrofitRx Java

Android Problem Overview


While using RxJava and Retrofit 2 I am trying to create Unit Tests to cover when my app receives specific responses.

The issue I have is that with Retrofit 2 I cannot see a nice way of creating a retrofit.Response object without the use of reflection.

@Test
public void testLogin_throwsLoginBadRequestExceptionWhen403Error() {


    Request.Builder requestBuilder = new Request.Builder();
    requestBuilder.get();
    requestBuilder.url("http://localhost");

    Response.Builder responseBuilder = new Response.Builder();
    responseBuilder.code(403);
    responseBuilder.protocol(Protocol.HTTP_1_1);
    responseBuilder.body(ResponseBody.create(MediaType.parse("application/json"), "{\"key\":[\"somestuff\"]}"));
    responseBuilder.request(requestBuilder.build());

    retrofit.Response<LoginResponse> aResponse = null;

    try {
        Constructor<retrofit.Response> constructor= (Constructor<retrofit.Response>) retrofit.Response.class.getDeclaredConstructors()[0];
        constructor.setAccessible(true);
        aResponse = constructor.newInstance(responseBuilder.build(), null, null);
    } catch (Exception ex) {
        //reflection error
    }

    doReturn(Observable.just(aResponse)).when(mockLoginAPI).login(anyObject());

    TestSubscriber testSubscriber = new TestSubscriber();
    loginAPIService.login(loginRequest).subscribe(testSubscriber);

    Throwable resultError = (Throwable) testSubscriber.getOnErrorEvents().get(0);
    assertTrue(resultError instanceof LoginBadRequestException);
}

I have tried using the following but this creates an OkHttp Response vs a Retrofit Response.

    Request.Builder requestBuilder = new Request.Builder();
    requestBuilder.get();
    requestBuilder.url("http://localhost");

    Response.Builder responseBuilder = new Response.Builder();
    responseBuilder.code(403);
    responseBuilder.protocol(Protocol.HTTP_1_1);

Android Solutions


Solution 1 - Android

The retrofit.Response class has static factory methods to create instances:

public static <T> Response<T> success(T body) {
  /* ... */
}

public static <T> Response<T> success(T body, com.squareup.okhttp.Response rawResponse) {
  /* ... */
}

public static <T> Response<T> error(int code, ResponseBody body) {
  /* ... */
}

public static <T> Response<T> error(ResponseBody body, com.squareup.okhttp.Response rawResponse) {
  /* ... */
}

For example:

Account account = ...;
retrofit.Response<Account> aResponse = retrofit.Response.success(account);

Or:

retrofit.Response<Account> aResponse = retrofit.Response.error(
  403, 
  ResponseBody.create(
    MediaType.parse("application/json"),
    "{\"key\":[\"somestuff\"]}"
  )
);

Note: In latest Retrofit version (2.7.1) for Kotlin, it recommends to use extension method like this:

Response.error(
  400,
  "{\"key\":[\"somestuff\"]}"
    .toResponseBody("application/json".toMediaTypeOrNull())
)

This falls under Effective Java Item 1: Consider static factory methods instead of constructors.

Solution 2 - Android

Heres how to mock just the retrofit responses

First you need to add these dependencies in build.gradle:

// mock websever for testing retrofit responses
testImplementation "com.squareup.okhttp3:mockwebserver:4.6.0"
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"

Mock a successful 200 response:

val mockResponseBody = Mockito.mock(MoviesResponse::class.java)
val mockResponse = Response.success(mockResponseBody) 

Mock an unsuccessful response (eg 400, 401, 404):

val errorResponse = 
    "{\n" +
    "  \"type\": \"error\",\n" +
    "  \"message\": \"What you were looking for isn't here.\"\n"
    + "}"
val errorResponseBody = errorResponse.toResponseBody("application/json".toMediaTypeOrNull())
val mockResponse = Response.error<String>(400, errorResponseBody)

No need to create a mock webserver and all that extra work.

Solution 3 - Android

A Kotlin + Mockito + okhttp3 example using Response.Builder

val mockResponse: Response<MyResponseReturnType> =
            Response.success(mock<MyResponseReturnType>(),
                    okhttp3.Response.Builder()
                            .code(200)
                            .message("Response.success()")
                            .protocol(Protocol.HTTP_1_1)
                            .request(Request.Builder().url("http://test-url/").build())
                            .receivedResponseAtMillis(1619053449513)
                            .sentRequestAtMillis(1619053443814)
                            .build())

Solution 4 - Android

Instead of using the toResponseBody() extension function on a String, I used it on a Byte Array like shown below.

Response.error(400, byteArrayOf().toResponseBody())

Doing byteArrayOf().toResponseBody() does create a ResponseBody.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGraham SmithView Question on Stackoverflow
Solution 1 - AndroidnhaarmanView Answer on Stackoverflow
Solution 2 - AndroidHaider MalikView Answer on Stackoverflow
Solution 3 - AndroiddianakarenmsView Answer on Stackoverflow
Solution 4 - AndroidMightianView Answer on Stackoverflow