fbpx

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.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.

https://square.github.io/retrofit/

Retrofit website

https://github.com/square/okhttp/tree/master/mockwebserver

MockWebServer

more insights

Uncategorized
Jarosław Michalik

#kotlinDevChallenge 2023 summary

The KotlinDevChallenge was not just a competition, but a journey of learning, growth, and community engagement. Every participant in this challenge is a winner in

Read More »
KotlinDevChallenge2023
Jarosław Michalik

#kotlinDevChallenge – 24

What do you love most about Kotlin? Productivity increase? Idioms? Or that it isn’t Java? Maybe something else! Share it. It can be short. It

Read More »
KotlinDevChallenge2023
Jarosław Michalik

#kotlinDevChallenge – 23

Challenge checking your generics knowledge 🙂 No long description is necessary. You can select more than one answer. Good luck!

Read More »

AndroidPro newsletter 🚀

join 3057 developers reading AndroidPro newsletter every week

👉 tips & tricks, articles, videos

👉 every Thursday in your inbox

🎁 bonus on signup – Clean Architecture blueprint

brought to you by Jarek Michalik, Kotlin GDE

You can unsubscribe any time. For details visit our privacy policy.

android pro newsletter tips and tricks how to learn android