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

23,652

Solution 1

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

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

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())
Share:
23,652

Related videos on Youtube

Graham Smith
Author by

Graham Smith

Developer, Creative and Entrepreneur are some of the titles applied to me. I have a passion for native mobile development, in particular Android, along with a strong background in SOA and distributed systems. I mentor new startups in the wider community. Previous Co-Organiser for NottsJS, a local meetup for JavaScript developers for 2+ years. 2010 Regional Contribution to Business Award Winner, awarded by the BCS “for the most promising year in industry student working in a computing field”. ► Native App Development ► Android (Android SDK) ► Kotlin, Java &amp; C# ► Distributed Systems (APIs / REST) ► Cloud Technology (AWS) ► .NET Core ► Coaching / Mentoring / Line Management

Updated on July 08, 2022

Comments

  • Graham Smith
    Graham Smith almost 2 years

    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);
    
  • Graham Smith
    Graham Smith over 8 years
    That's brilliant thank you! Just a couple of points, how do you get a typed Response object (thinking it might be good to illustrate for a more complete answer) and do you have a link to the last part you mentioned for a complete text/article? I will test tomorrow AM and update. Thanks again!
  • nhaarman
    nhaarman over 8 years
    Storing the result directly in a typed variable (as I have done now) requires no extra difficulties. For some rare cases, you might need to give explicit type arguments like so: Response.<Account>error(....).
  • nhaarman
    nhaarman over 8 years
    Effective Java is a book by Joshua Bloch, which is readily available.
  • Graham Smith
    Graham Smith over 8 years
    Yes this works thanks. Only thing I cannot seem to get working is the line: retrofit.Response<Account> aResponse = retrofit.Response.success(account); I get a class cast exception (note the word success is spelt right in my version)
  • nhaarman
    nhaarman over 8 years
    Exactly that does work over here. What is the message that comes with it?
  • Graham Smith
    Graham Smith over 8 years
    ` MyObject thing = new MyObject(); retrofit.Response<MyObject> aResponse = retrofit.Response.success(thing);` returns at runtime java.lang.ClassCastException: com.a.project.MyObject cannot be cast to retrofit.Response
  • Mr.G
    Mr.G almost 7 years
    @nhaarman i cannot import Response . class . using rectrofit 2
  • Mahdi
    Mahdi over 6 years
    I am trying to mock Retrofit respond not in test but in main code. but putting code 401 not lead to call authenticate methods