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

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)
                put("responseBody", runCatching { JSONObject(body) }.getOrNull())
                put("serviceName", serviceName)
            }

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

            reportingClient.newCall(reportRequest).execute().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.your-domain.com"}\""
        )
        buildConfigField(
            "String", "MOCK_API_KEY",
            "\"${project.findProperty("MOCK_API_KEY") ?: ""}\""
        )
    }
}

Add to your local.properties:

MOCK_ALPHA_URL=https://mock-alpha.your-domain.com
MOCK_API_KEY=<your-key>

The key is generated when you create a project in Mock Alpha and can be found in Project Settings. With a deployed (HTTPS) instance, this is all the configuration you need.

4. Local instance only — allow cleartext HTTP

Skip this step if you use a deployed HTTPS instance. If Mock Alpha runs on your machine (http://10.0.2.2:3000 from the emulator), Android blocks cleartext HTTP by default. Add res/xml/network_security_config.xml:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="false">10.0.2.2</domain>
    </domain-config>
</network-security-config>

Reference it from AndroidManifest.xml:

<application
    android:networkSecurityConfig="@xml/network_security_config"
    ...>

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.

Choosing MOCK_ALPHA_URL

EnvironmentMock Alpha URL
Deployed instance (recommended)https://mock-alpha.your-domain.com
Local + Android Emulatorhttp://10.0.2.2:3000
Local + physical device (same Wi-Fi)http://192.168.x.x:3000
Local + physical device (USB tunnel)http://localhost:3000 (after adb reverse tcp:3000 tcp:3000)

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.your-domain.com: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
}

(For a local instance from the emulator, use tcp://10.0.2.2:1883.)

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

On this page