Have you ever worked in a project where the backend was terrible? Did you need to test against production to have a bearable developing experience?

If that’s the case, today I want to share with you a solution where we are going to mock the responses from the server with code, no need to use external tools like Charles.

With this approach, a JSON stored in our Android project is going to be returned each time we make a request to the backend, not only making our developing experience manageable, but improving response times while testing the code and saving money on API calls.

As we are going to have JSON files inside the project for this, we could later use them to write unit and integration tests, where we would usually create fake responses directly in code.

However, not everything is perfect, as we are not really testing our app as a usual QA process would do, as we are testing our JSON, and then behaviour could change once users get into the app.

We cannot verify at a 100% that mock backend responses are the same as what is truly going to happen, as we would have with a pre-production server, so keep a close relationship with other teams to avoid problems with this.

We are going to need to be very careful when editing and adding new JSON response bodies to mock.

Note that although it is possible to do, and it’s not hard, for this article we are going to see how to implement this functionality for successful responses, not failed ones.

Without further ado, let’s see how it’s done.

How are we going to do it?

For this tutorial I’m going to use Retrofit as it’s the network library that I have more experience with, but I don’t think it would be difficult to port to other alternatives like Ktor so that you can use it in KMM projects.

Essentially, what we are going to do is create an Interceptor that is going to take the network call, and if we are in debug mode, (defined by our build variant) is going to create a response based on the JSON that is specified in one of the headers of the request.

If we are in release mode, what we are going to do in this Interceptor is just removing the header and pass the call as if nothing would have happened.

Sadly, this implementation is not compatible with the Network Inspector integrated in Android Studio.

We won’t be able to debug using that tool unless we attack a real server, but as the fake JSON is specified in the header of the call, it’s pretty easy to find it and see what could be going on.

The rest of the logic is pretty straightforward, so let’s jump into the code and see how it’s done.

Implementing the code

First, as usual, let’s see the dependencies we need to add to the project.

I’m using version catalog with Gradle KTS, but you can easily extract the dependencies from the code. Either way, I strongly suggest that you check this article where I talk about version catalog.

We are going to need, Retrofit, OkHttp3 and Koin (this is just my choice, use whatever dependency injection library you want, as is not mandatory).

retrofit = "2.9.0"
okhttp3 = "4.10.0"
koin = "3.3.2"

retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
retrofit-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }
okhttp3  = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp3" }
okhttp3-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp3" }
koin = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" }
convertergson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }

Once we add these to our Gradle project, and sync, we can now start with Kotlin code.

Let’s imagine we want to retrieve articles from my incredible blog. For that we need a GET define like so.

interface MockApiService {
    @Headers("$MOCK_RESPONSE_HEADER: getArticles.json")
    suspend fun getArticles(): List<String>

This is a usual Retrofit call, but with the characteristic that in the @Headers tag we are adding a constant and the JSON that we want to respond with while developing.

Once we have the interface with the API calls defined, we need to set up Retrofit as usual.

private const val BASE_URL = "https://molidevwrites.com/"
private val interceptor: FakeResponseInterceptor = FakeResponseInterceptor()
private val client: OkHttpClient = OkHttpClient.Builder().apply {
private val retrofit =

object MockApi {
    val retrofitService: MockApiService by lazy {

The only thing that you need to pay attention to, is the client variable which has an Interceptor added with the logic that we talked about in the previous section.

The interceptor has the following code.

const val SUCCESS_CODE = 200

class FakeResponseInterceptor : Interceptor {

    private val assetReader: JsonReader by inject(JsonReader::class.java)

    override fun intercept(chain: Interceptor.Chain): Response {
        return if (BuildConfig.DEBUG) {
        } else {

    private fun handleMockResponse(chain: Interceptor.Chain): Response {
        val headers = chain.request().headers
        val responseString = assetReader.getJsonAsString(headers[MOCK_RESPONSE_HEADER])

        return chain.proceed(chain.request())
            .addHeader("content-type", "application/json")

Depending on the BuildConfig.DEBUG variable, we are going to mock the response or attack production, which is defined in our Gradle project file.

buildTypes {
        debug {
            buildConfigField("Boolean", "DEBUG", "true")
        release {
            buildConfigField("Boolean", "DEBUG", "false")

Once it enters the handleMockResponse method, we are going to use the JsonReader class we are injecting with Koin. This class is going to return the JSON specified in the headers as a string so that we can add it to the response body.

The JsonReader class looks like so.

class JsonReader(
    private val assetManager: AssetManager,
) {
    fun getJsonAsString(jsonFileName: String?): String {
        val content = StringBuilder()
        val reader = BufferedReader(getJsonInputStream(jsonFileName).reader())
        var line = reader.readLine()
        while (line != null) {
            line = reader.readLine()
        return content.toString()

    private fun getJsonInputStream(jsonFileName: String?): InputStream {
        val jsonFilePath = String.format("%s", jsonFileName)
        return assetManager.open(jsonFilePath)

It just reads from the assets directory, as easy as that. It would be a good idea to keep it organized with subdirectories, but for the sake of simplicity, we are just going to read from the root. This class uses the AssetManager provided by the Android framework.

In case you’ve never created the assets directory, here is how you can do it.

And that’s all, to test it works I’ve added in the MainActivity this code, you must not do this, but to keep it as simple as possible, so that you can see that it works and test it by yourself with the repo, I’m putting blocking logic in the Activity.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {

        setContent {
            RetrofitmockresponseTheme {
                    modifier = Modifier.fillMaxSize(),
                    verticalArrangement = Arrangement.Center,
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    var response: List<String> by remember {
                        mutableStateOf(List(1) { "No Articles" })
                    Greeting("I have ${response.size} articles and the first one is: \n ${response.first()}")

                        modifier = Modifier.size(200.dp, 200.dp).padding(20.dp),
                        onClick = { response = getArticles()}) {
                          Text(text = "Get Articles!")

private fun getArticles(): List<String> = runBlocking {
    return@runBlocking withContext(Dispatchers.IO) {
        return@withContext MockApi.retrofitService.getArticles()

You can check the full repository with the code here on GitHub.


Featured image by AbsolutVision on Unsplash

If you want to read more content like this without ads and support me, don’t forget to check my profile, or give Medium a chance by becoming a member to access unlimited stories from me and other writers. It’s only $5 a month and if you use this link I get a small commission.

Categorized in: