network-tests

star 1.5k

Use when writing NetworkRule integration tests in stripe-android — covers testBodyFromFile, inline JSON modification, request matchers, and fixture patterns

stripe By stripe schedule Updated 5/5/2026

name: network-tests description: Use when writing NetworkRule integration tests in stripe-android — covers testBodyFromFile, inline JSON modification, request matchers, and fixture patterns

NetworkRule Integration Tests

For instrumentation tests that need mocked network responses, use NetworkRule (from network-testing module) with testBodyFromFile. JSON fixture files live in the module's src/androidTest/resources/ directory (e.g., paymentsheet/src/androidTest/resources/checkout-session-init.json) and are resolved by filename from the resources root.

Basic Structure

@RunWith(AndroidJUnit4::class)
internal class MyFeatureTest {
    @get:Rule
    val testRules: TestRules = TestRules.create()

    private val networkRule = testRules.networkRule

    @Test
    fun testSomething() = runPaymentSheetTest(
        networkRule = networkRule,
        resultCallback = ::assertCompleted,
    ) { testContext ->
        networkRule.enqueue(requestMatcher) { response ->
            response.testBodyFromFile("my-fixture.json")
        }
        // ... test actions ...
    }
}

Prefer Inline JSON Modification Over New Fixture Files

When a test needs a modified JSON response, use the testBodyFromFile lambda to modify the base fixture inline — do NOT create a separate JSON file for each variation.

// GOOD: Modify the base fixture inline
networkRule.checkoutInit { response ->
    response.testBodyFromFile("checkout-session-init.json") { json ->
        json.put("customer_email", "session@example.com")
    }
}

// BAD: Creating checkout-session-init-with-email.json with one field different
networkRule.checkoutInit { response ->
    response.testBodyFromFile("checkout-session-init-with-email.json")
}

This keeps the fixture set minimal and makes the test-specific modifications explicit at the call site.

Composing Multiple Modifications

The lambda receives a JSONObject — use standard org.json methods to add or modify fields:

networkRule.checkoutInit { response ->
    response.testBodyFromFile("checkout-session-init.json") { json ->
        json.put("customer", JSONObject("""
            {
                "id": "cus_12345",
                "payment_methods": [],
                "can_detach_payment_method": true
            }
        """.trimIndent()))
        json.put("customer_managed_saved_payment_methods_offer_save", JSONObject("""
            {"enabled": true, "status": "not_accepted"}
        """.trimIndent()))
    }
}

For nested modifications, chain getJSONObject():

response.testBodyFromFile("checkout-session-init.json") { json ->
    json.getJSONObject("server_built_elements_session_params")
        .getJSONObject("deferred_intent")
        .put("setup_future_usage", "off_session")
}

Extracting Shared Modifiers

When multiple tests share the same JSON modification, extract the lambda as a parameter:

private fun runMyTest(
    jsonModifier: (JSONObject) -> Unit = {},
) = runPaymentSheetTest(networkRule = networkRule, resultCallback = ::assertCompleted) { testContext ->
    networkRule.checkoutInit { response ->
        response.testBodyFromFile("checkout-session-init.json", jsonModifier)
    }
    // ... shared test logic ...
}

@Test
fun testWithSfu() = runMyTest { json ->
    json.getJSONObject("server_built_elements_session_params")
        .getJSONObject("deferred_intent")
        .put("setup_future_usage", "off_session")
}

testBodyFromFile Variants

Signature Use when
testBodyFromFile("file.json") No modifications needed
testBodyFromFile("file.json") { json -> ... } Modifying JSON fields inline
testBodyFromFile("file.json", replacements) String-level find/replace with ResponseReplacement

Request Matchers

Use RequestMatchers (from com.stripe.android.networktesting.RequestMatchers) to validate request body parameters. Import the matchers you need statically:

import com.stripe.android.networktesting.RequestMatchers.bodyPart
import com.stripe.android.networktesting.RequestMatchers.hasBodyPart
import com.stripe.android.networktesting.RequestMatchers.not

networkRule.checkoutConfirm(
    bodyPart("expected_amount", "5099"),
    not(hasBodyPart("save_payment_method")),
) { response ->
    response.testBodyFromFile("checkout-session-confirm.json")
}

bodyPart Encoding

bodyPart(), hasBodyPart(), and query(name, value) auto-decode both the matcher arguments and the request body before comparing. Use plain readable strings — urlEncode() is unnecessary:

// Keys with brackets and values with special characters — just use plain strings
bodyPart("billing_details[email]", "user@example.com")
bodyPart("billing_details[address][line1]", "123 Main St")
bodyPart("payment_method_data[allow_redisplay]", "unspecified")

// urlEncode() still works (backward compatible) but is unnecessary
// bodyPart(urlEncode("billing_details[email]"), urlEncode("user@example.com"))

Mismatch Debugging

When a request doesn't match a mock, the error message shows per-matcher diagnostics with the nearest-miss mock:

POST https://localhost/v1/payment_intents/pi_123/confirm
  Body params: {billing_details[email]=actual@test.com, payment_method=pm_123}
  Nearest mock: composite(path(/v1/confirm), bodyPart(billing_details[email], expected@test.com))
    + PASS: path(/v1/confirm)
    + PASS: method(POST)
    - FAIL: bodyPart(billing_details[email], expected@test.com)

See PaymentSheetBillingConfigurationTest.kt for more examples.

Install via CLI
npx skills add https://github.com/stripe/stripe-android --skill network-tests
Repository Details
star Stars 1,500
call_split Forks 716
navigation Branch main
article Path SKILL.md
More from Creator