Network-dependent Android screens are hard to test when the UI waits for a live backend that can be slow, unavailable, or filled with changing data. A local MockWebServer fixture lets an Espresso test feed one known HTTP response to the app and assert the rendered screen state that response should produce.

MockWebServer runs inside the instrumented test process and exposes an HTTP base URL that the app can use during the test. The app still exercises its real HTTP client, JSON parser, repository, and view code, but the backend response comes from the queued fixture instead of a remote service.

Install the network override before launching the activity so cached clients do not keep the production URL. Keep the override and any cleartext permission in debug or androidTest wiring, and reset both the app hook and server after each test.

Steps to use MockWebServer network mocks in Espresso tests:

  1. Add the Espresso, AndroidX Test, and MockWebServer dependencies to the app module.
    build.gradle.kts
    android {
        defaultConfig {
            testInstrumentationRunner =
                "androidx.test.runner.AndroidJUnitRunner"
        }
    }
     
    val espressoVersion = "3.7.0"
    val androidxTestVersion = "1.7.0"
    val junitVersion = "1.3.0"
    val mockWebServerVersion = "5.4.0"
     
    dependencies {
        androidTestImplementation(
            "androidx.test.espresso:" +
                "espresso-core:$espressoVersion"
        )
        androidTestImplementation(
            "androidx.test:core:$androidxTestVersion"
        )
        androidTestImplementation(
            "androidx.test:runner:$androidxTestVersion"
        )
        androidTestImplementation(
            "androidx.test.ext:junit:$junitVersion"
        )
        androidTestImplementation(
            "com.squareup.okhttp3:" +
                "mockwebserver:$mockWebServerVersion"
        )
    }

    The versions shown match the current AndroidX Test releases in Google Maven and the current MockWebServer release in Maven Central. Use the app's version catalog or dependency convention when the project already centralizes test libraries.

  2. Route the app API base URL through a test-overridable provider.
    NetworkConfig.kt
    package com.example.orders.net
     
    import okhttp3.HttpUrl
    import okhttp3.HttpUrl.Companion.toHttpUrl
     
    object NetworkConfig {
        private val productionBaseUrl =
            "https://api.example.com/".toHttpUrl()
     
        @Volatile
        var testBaseUrl: HttpUrl? = null
     
        val ordersBaseUrl: HttpUrl
            get() = testBaseUrl ?: productionBaseUrl
     
        fun resetForTests() {
            testBaseUrl = null
        }
    }

    Use the app's existing dependency injection or service locator when it already owns API construction. The important boundary is that the test can set the base URL before the activity, repository, or Retrofit service is created.

  3. Build the API client from the overridable base URL.
    OrderApiFactory.kt
    package com.example.orders.net
     
    import retrofit2.Retrofit
    import retrofit2.converter.moshi.MoshiConverterFactory
     
    object OrderApiFactory {
        fun create(): OrderApi {
            return Retrofit.Builder()
                .baseUrl(NetworkConfig.ordersBaseUrl)
                .addConverterFactory(MoshiConverterFactory.create())
                .build()
                .create(OrderApi::class.java)
        }
    }

    Do not let an androidTest run keep a production API singleton that was created before the override. Reset cached repositories or recreate the dependency graph before launching the screen under test.

  4. Allow the debug build to call the local HTTP server when cleartext traffic is blocked.
    src/debug/AndroidManifest.xml
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
        <application android:usesCleartextTraffic="true" />
    </manifest>

    Keep cleartext permission in debug or test-only configuration. Do not loosen the release app network policy only to make a mock server test pass.

  5. Create the MockWebServer backed Espresso test.
    OrderNetworkMockTest.kt
    package com.example.orders
     
    import androidx.test.core.app.ActivityScenario
    import androidx.test.espresso.Espresso.onView
    import androidx.test.espresso.action.ViewActions.click
    import androidx.test.espresso.assertion.ViewAssertions.matches
    import androidx.test.espresso.matcher.ViewMatchers.withId
    import androidx.test.espresso.matcher.ViewMatchers.withText
    import androidx.test.ext.junit.runners.AndroidJUnit4
    import androidx.test.filters.LargeTest
    import com.example.orders.net.NetworkConfig
    import java.util.concurrent.TimeUnit
    import okhttp3.mockwebserver.MockResponse
    import okhttp3.mockwebserver.MockWebServer
    import org.junit.After
    import org.junit.Assert.assertEquals
    import org.junit.Before
    import org.junit.Test
    import org.junit.runner.RunWith
     
    @RunWith(AndroidJUnit4::class)
    @LargeTest
    class OrderNetworkMockTest {
        private lateinit var server: MockWebServer
     
        @Before
        fun startServer() {
            server = MockWebServer()
            server.start()
            NetworkConfig.testBaseUrl = server.url("/")
            AppGraph.resetForTests()
        }
     
        @After
        fun stopServer() {
            NetworkConfig.resetForTests()
            AppGraph.resetForTests()
            server.shutdown()
        }
     
        @Test
        fun openOrdersShowsMockedOrder() {
            server.enqueue(
                MockResponse()
                    .setResponseCode(200)
                    .setHeader("Content-Type", "application/json")
                    .setBody(
                        """
                        {
                          "orders": [
                            {
                              "id": "A-104",
                              "title": "Coffee beans",
                              "status": "Ready"
                            }
                          ]
                        }
                        """.trimIndent()
                    )
            )
     
            val scenario =
                ActivityScenario.launch(OrderActivity::class.java)
     
            try {
                onView(withId(R.id.loadOrdersButton)).perform(click())
     
                onView(withId(R.id.orderTitle))
                    .check(matches(withText("Coffee beans")))
     
                onView(withId(R.id.orderStatus))
                    .check(matches(withText("Ready")))
            } finally {
                scenario.close()
            }
     
            val request = server.takeRequest(2, TimeUnit.SECONDS)
            assertEquals("/orders", request?.path)
        }
    }

    MockWebServer controls the response data, not asynchronous timing. Register the app's IdlingResource or another Espresso-aware wait before this assertion when the screen updates after background work.
    Related: How to add an Espresso IdlingResource

  6. Replace the sample graph reset, activity, endpoint path, JSON body, view IDs, and expected text with values from the app under test.

    The UI assertion should prove that the mocked response reached the screen. The recorded-request assertion should prove that the app called the expected test server path instead of a live backend.

  7. Run the connected Espresso test task on an emulator or device.
    $ ./gradlew :app:connectedDebugAndroidTest
    
    Task :app:connectedDebugAndroidTest
    Starting 1 tests on Pixel_8_API_35
    OrderNetworkMockTest > openOrdersShowsMockedOrder PASSED
    
    BUILD SUCCESSFUL in 38s

    The task name changes with the module and variant. Android projects with product flavors may expose a task such as :app:connectedFreeDebugAndroidTest.