Testing Retrofit calls with OkHttp MockWebServer

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.ResponseBodyimport retrofit2.Callimport retrofit2.http.GETimport retrofit2.http.Queryinterface 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.ResponseBodyimport org.junit.jupiter.api.Testimport retrofit2.Callimport retrofit2.Retrofitimport retrofit2.http.GETimport retrofit2.http.Queryimport strikt.api.expectThatclass 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.Singleimport okhttp3.HttpUrlimport okhttp3.OkHttpClientimport okhttp3.ResponseBodyimport okhttp3.mockwebserver.MockResponseimport okhttp3.mockwebserver.MockWebServerimport okhttp3.mockwebserver.RecordedRequestimport org.junit.jupiter.api.Testimport retrofit2.Retrofitimport retrofit2.adapter.rxjava2.RxJava2CallAdapterFactoryimport retrofit2.http.GETimport retrofit2.http.Queryimport strikt.api.expectThatclass 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

Leave a Comment