Using local JSON files as an API placeholder with Retrofit

With this snippet you can easily and quickly mock an API during developmentby placing JSON files in your res/raw/ folder. This comes in handy when the actual API does not exist yet.

For mocking an API for unit tests, have a look at Retrofit’s MockService (GitHub).

Retrofit

When creating your Retrofit instance, you can add a MockIntercepter which transforms each request into reading from res/raw/ instead:

val mockInterceptor = MockInterceptor()
mockClient = OkHttpClient.Builder()
    .addInterceptor(mockInterceptor)
    .build()

In your Retrofit instance, use this client instead of the regular client. For example:

private val retrofit = Retrofit.Builder()
        .baseUrl(BuildConfig.ROOT_URL_PRODUCTION)
        .client(if (BuildConfig.DEBUG) mockClient else okHttpClient)
        .addConverterFactory(MoshiConverterFactory.create())
        .build()

Mock Interceptor

The Mock Interceptor transforms the requests to load the response from a file:

class MockInterceptor : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        val url = chain.request().url()
        val path = "res/raw/${url.getLastPathSegment()}"
        val response = path.readFile(this)

        require(!response.isNullOrEmpty()) { "JSON file $path should exist and not be empty" }

        return Response.Builder()
            .code(200)
            .message(response)
            .request(chain.request())
            .protocol(Protocol.HTTP_1_1)
            .body(ResponseBody.create(MediaType.parse("application/json"), response))
            .addHeader("content-type", "application/json")
            .build()
    }
}

This assumes the following extension functions:

/**
 * Opens an InputStream to the specified path.
 * Pro tip: By prefixing `raw/` or `res/raw/` to the String you can load files from res/raw without a Context.
 */
fun String.openStream(clz: Any) = clz.javaClass.classLoader?.getResourceAsStream(this)

fun InputStream.readFile() = this.bufferedReader().use(BufferedReader::readText)

fun String.readFile(clz: Any) = this.openStream(clz)?.readFile()

/**
 * Added a convenient shortcut here because Android's Uri class
 * has a getLastPathSegment method but OkHttp does not.
 */
fun HttpUrl.getLastPathSegment(): String = this.pathSegments().last()

Api Service

Now, if your ApiService looks like this:

interface ApiService {
    @GET("example.json")
    suspend fun getExample(): ExampleResponse
}

The file will be loaded from res/raw/example.json instead.