Retrofit - probably the most popular networking client in Android development. Basically it allows to create HTTP client in an interface - you just add annotation with HTTP method, relative or absolute path and proper request is constructed. Retrofit does not generate code in compile time - it creates implementations in runtime.


import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query

interface RemoteApi {
    @GET("http://some.host/api/data")
    fun searchByPhrase(@Query("search") searchPhrase: String): Call<ResponseBody>
}
Example of Retrofit interface

The method invocation:

remoteApi.searchByPhrase("asd")

Will create GET request to:

http://some.host/api/data?search=asd 

Now we will try to come up with a way to check if proper requests are constructed.

Basic test case

We will consider the following Retrofit interface with given configuration:

fun remoteApi(baseUrl: String): RemoteApi {
    return Retrofit.Builder()
            .client(OkHttpClient())
            .baseUrl(baseUrl)
            .build()
            .create(RemoteApi::class.java)
}

interface RemoteApi {
    @GET("/api/data")
    fun searchByPhrase(@Query("search") searchPhrase: String): Call<ResponseBody>
}
Retrofit API with regular Call<T>

How could we actually perform some assertions on that? Was actually GET method used? Was search phrase included in query?

While we are using retrofit2.Call<T> we have Call.request() method available directly and we can perform assertions on Request object:

import okhttp3.ResponseBody
import org.junit.jupiter.api.Test
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.http.GET
import retrofit2.http.Query
import strikt.api.expectThat

class RetrofitTest {
    @Test
    fun `it should GET with query`() {

        val remoteApi = remoteApi(baseUrl = "http://some.api")

        val givenSearchQuery = "given search phrase"

        val call: Call<ResponseBody> = remoteApi.searchByPhrase(givenSearchQuery)

        expectThat(call.request()) {
            assertThat("is GET method") {
                it.method() == "GET"
            }
            assertThat("has given search query") {
                it.url().queryParameterValues("search") == listOf(givenSearchQuery)
            }
        }
    }
}
Test case - assertions on call.request()

Test case with non-standard call adapter factory

In my opinion the power of Retrofit comes from call and converter adapter factories - instead of relying on built-in types (such as retrofit2.Call) you can add RxJava call adapter and framework would find a way to convert Call<T> into rx Single<T>. Unfortunately we won't have direct access to Call.request() method then!

fun remoteApi(baseUrl: HttpUrl): RemoteApi {
    return Retrofit.Builder()
            .client(OkHttpClient())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .baseUrl(baseUrl)
            .build()
            .create(RemoteApi::class.java)
}

interface RemoteApi {
    @GET("/api/data")
    fun searchByPhrase(@Query("search") searchPhrase: String): Single<ResponseBody>
}
Retrofit API with RxJava2 call adapter factory

To handle this case and have request to perform assertions on I came up with the following approach:

  1. Use OkHttp MockWebServer to record incoming requests
  2. Wrap system under test invocation with RemoteApi as lambda parameter and RecordedRequest as return type
  3. Perform assertions on RecordedRequest
import io.reactivex.Single
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.ResponseBody
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest
import org.junit.jupiter.api.Test
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.http.GET
import retrofit2.http.Query
import strikt.api.expectThat

class RetrofitTest {
    @Test
    fun `it should GET with query`() {

        val givenSearchQuery = "given search phrase"

        val request: RecordedRequest = takeMockRequest {
            searchByPhrase(givenSearchQuery)
                    .subscribe()
        }

        expectThat(request) {
            assertThat("is GET method") {
                it.method == "GET"
            }
            assertThat("has given search query") {
                it.requestUrl.queryParameterValues("search") == listOf(givenSearchQuery)
            }
        }
    }

    private fun takeMockRequest(sut: RemoteApi.() -> Unit): RecordedRequest {
        return MockWebServer()
                .use {
                    it.enqueue(MockResponse())
                    it.start()
                    val url = it.url("/")

                    sut(remoteApi(url))

                    it.takeRequest()
                }
    }
}
Test case with RecordedRequest

I highly encourage you to explore more methods and properties available in RecorderRequest - you may find there more things that may be useful in your test.


Now we have a way to perform assertions on request that was recorded in MockWebServer - with this approach we can design even more complex integration test suites.

Retrofit
A type-safe HTTP client for Android and Java
Retrofit website
square/okhttp
Square’s meticulous HTTP client for Java and Kotlin. - square/okhttp
MockWebServer

Get new posts directly to your inbox, subscribe to our newsletter

* indicates required

For GDPR compliance, I have to ask you to allow me to send emails to you. I will use your email address to notify you about new content on my site.

You can unsubscribe at any time by clicking the link in the footer of our emails. For information about our privacy practices, please visit our website.

We use Mailchimp as our marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp's privacy practices here.