Mock Alpha

Android Interceptor

OkHttp interceptor that routes traffic through Mock Alpha in debug builds

Android Interceptor

A reference implementation of the capture-and-mock interceptor pattern (see Client Integration) for Android, built on OkHttp. When enabled, it sends every request through Mock Alpha and simultaneously reports the real response back so Mock Alpha can capture and seed scenarios.

Android is just one platform — the same pattern works anywhere. See Platform Examples for Flutter, Web, iOS, and curl versions.

Setup

1. Add the interceptor class

Create MockAlphaInterceptor.kt in your Android project:

import okhttp3.Call
import okhttp3.Callback
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.json.JSONObject
import java.io.IOException

class MockAlphaInterceptor(
    private val mockAlphaUrl: String = BuildConfig.MOCK_ALPHA_URL,
    private val enabled: Boolean = BuildConfig.DEBUG,
    private val apiKey: String = BuildConfig.MOCK_API_KEY,
    private val serviceName: String = "android-app",
) : Interceptor {

    private val reportingClient = OkHttpClient()

    override fun intercept(chain: Interceptor.Chain): Response {
        val original = chain.request()

        if (!enabled) return chain.proceed(original)

        // 1. Get the real response
        val realResponse = chain.proceed(original)
        val bodyString = realResponse.body?.string() ?: ""

        // 2. Report to Mock Alpha (fire-and-forget)
        reportCapture(original, realResponse.code, bodyString)

        // 3. Re-route the request through Mock Alpha mock endpoint
        val mockRequest = original.newBuilder()
            .url("$mockAlphaUrl/api/mock${original.url.encodedPath}".toHttpUrl()
                .newBuilder()
                .encodedQuery(original.url.encodedQuery)
                .build())
            .header("X-Api-Key", apiKey)
            .build()

        return try {
            chain.proceed(mockRequest)
        } catch (e: Exception) {
            // Fallback to real response if mock is unavailable
            realResponse.newBuilder()
                .body(bodyString.toByteArray().let {
                    okhttp3.ResponseBody.create("application/json".toMediaType(), it)
                })
                .build()
        }
    }

    private fun reportCapture(request: Request, statusCode: Int, body: String) {
        try {
            val payload = JSONObject().apply {
                put("method", request.method)
                put("path", request.url.encodedPath)
                put("statusCode", statusCode)
                // Send the raw body string as-is. Mock Alpha parses it server-side,
                // so objects, arrays, and non-JSON bodies are all captured correctly.
                // Do NOT pre-parse with JSONObject(body): it throws on arrays, and
                // JSONObject.put(key, null) silently drops the key — the capture
                // would arrive without a responseBody and be rejected.
                put("responseBody", body)
                put("serviceName", serviceName)
            }

            val reportRequest = Request.Builder()
                .url("$mockAlphaUrl/api/collect")
                .header("X-Api-Key", apiKey)
                .post(payload.toString().toRequestBody("application/json".toMediaType()))
                .build()

            // Async fire-and-forget — never blocks the request thread
            reportingClient.newCall(reportRequest).enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) { /* ignore */ }
                override fun onResponse(call: Call, response: Response) { response.close() }
            })
        } catch (_: Exception) {
            // Silent — reporting failures must not crash the app
        }
    }
}

2. Register the interceptor

In your OkHttpClient builder (typically in a Hilt or Koin module):

val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(MockAlphaInterceptor(apiKey = BuildConfig.MOCK_API_KEY))
    .build()

3. Add the URL and API key to build config

In app/build.gradle.kts:

android {
    defaultConfig {
        buildConfigField(
            "String", "MOCK_ALPHA_URL",
            "\"${project.findProperty("MOCK_ALPHA_URL") ?: "https://mock-alpha.duckdns.org"}\""
        )
        buildConfigField(
            "String", "MOCK_API_KEY",
            "\"${project.findProperty("MOCK_API_KEY") ?: ""}\""
        )
    }
}

Add to your local.properties:

MOCK_ALPHA_URL=https://mock-alpha.duckdns.org
MOCK_API_KEY=<your-key>

The key is generated when you create a project in Mock Alpha and can be found in Project Settings. Since the instance is served over HTTPS, this is all the configuration you need — no cleartext-traffic exceptions required.

How it works

Android Request

    ├── [real] → Backend API → real response → /api/collect → Mock Alpha captures

    └── [mock] → Mock Alpha /api/mock/... → scenario response → Android

On each request the interceptor:

  1. Forwards the request to the real backend and captures the response
  2. Reports it to /api/collect so Mock Alpha learns the endpoint shape
  3. Returns the mock scenario response from Mock Alpha

Once an endpoint is captured, you can switch scenarios from the dashboard. The Android app always gets Mock Alpha's response — your selected scenario.

MQTT connection

If you are using the MQTT mock server, connect your MQTT client with:

val mqttOptions = MqttConnectOptions().apply {
    userName = projectId        // the UUID shown in Project Settings
    password = apiKey.toCharArray()
}

val client = MqttClient("tcp://mock-alpha.duckdns.org:1883", clientId)
client.connect(mqttOptions)

// Subscribe without the projectId prefix — Mock Alpha adds it internally
client.subscribe("devices/status") { topic, message ->
    // handle mock MQTT events
}

See MQTT Mock Server for how to configure topics and payloads.

On this page