RESPX intercepts HTTPX requests and returns controlled responses. A test can cover success, errors, and timeouts without depending on network access, credentials, or external uptime.

Mock one route

python -m pip install pytest httpx respx
import httpx


def get_status(client: httpx.Client) -> str:
    response = client.get("https://api.example.com/status")
    response.raise_for_status()
    return response.json()["status"]


def test_get_status(respx_mock) -> None:
    route = respx_mock.get("https://api.example.com/status").respond(
        200, json={"status": "ok"}
    )
    with httpx.Client() as client:
        assert get_status(client) == "ok"
    assert route.called

Register only expected routes. An unexpected URL or method should fail because it may reveal a contract regression. Assert meaningful bodies, queries, and headers without coupling to irrelevant details.

The Python HTTPX guide covers clients and timeouts. For coroutines, combine RESPX with pytest-asyncio.

Failures and boundaries

Choose router scope deliberately

The respx_mock fixture resets routes after each pytest case. @respx.mock provides decorator-based isolation, while with respx.mock: limits interception to one block. Keep activation narrow so unrelated setup does not accidentally rely on a mock. Registered routes should explain every outbound request. An unmatched method, host, path, or query usually signals a regression. Avoid catch-all responses that turn those regressions into false positives.

Match the meaningful request contract

Routes can constrain method, URL, query parameters, headers, and content. Match only details the client promises. A pagination value belongs to the contract; incidental JSON key order does not.

def test_sends_page_and_token(respx_mock) -> None:
    route = respx_mock.get(
        "https://api.example.com/items",
        params={"page": "2"},
        headers={"authorization": "Bearer test-token"},
    ).respond(200, json={"items": []})

    with httpx.Client() as client:
        response = client.get(
            "https://api.example.com/items",
            params={"page": 2},
            headers={"authorization": "Bearer test-token"},
        )

    assert response.json() == {"items": []}
    assert route.call_count == 1

Never use a production token in a fixture. A clearly fake value proves header composition without introducing a secret.

Inspect the captured request

Route history exposes the actual httpx.Request. This is useful when matching every payload detail would obscure failures.

request = route.calls.last.request
assert request.method == "GET"
assert request.url.params["page"] == "2"

For JSON, decode the body and compare semantic structure rather than whitespace or key order. Assert credentials only through presence or synthetic values, and never print authorization headers in a custom failure. With several calls, verify count and the business-relevant sequence.

Cover AsyncClient

RESPX intercepts httpx.AsyncClient through the same router. Combine it with pytest-asyncio and await production code normally.

@pytest.mark.asyncio
async def test_async_status(respx_mock) -> None:
    route = respx_mock.get("https://api.example.com/status").respond(
        200, json={"status": "ok"}
    )
    async with httpx.AsyncClient() as client:
        assert await fetch_status(client) == "ok"
    assert route.called

Inject clients into application code. Hidden client construction makes timeouts, transports, base URLs, and lifecycle difficult to control. A fixture providing a shared async client must close it after yield.

Distinguish HTTP and transport failures

A 503 is a valid HTTP response and reaches raise_for_status(). httpx.ConnectTimeout, ReadTimeout, and ConnectError happen before a usable response exists. Use side_effect for transport failures:

respx_mock.get("https://api.example.com/status").mock(
    side_effect=httpx.ConnectTimeout("connection timed out")
)

Assert the stable error exposed by your application, not complete dependency wording. Verify that nonretryable 4xx responses are not retried and retryable failures obey the limit. Inject sleep or backoff so tests do not spend real seconds waiting.

Test retry sequences

A retry test should show the intermediate failure and final outcome. A route can return successive responses or use a callback that counts requests. Assert the final value and exact attempt count. Include exhaustion so off-by-one errors are visible.

Retry policy belongs to production code; RESPX only supplies observations. The application decides eligible errors, delay, and whether repetition is safe. Retrying a POST can duplicate an operation unless the provider supports idempotency keys.

Use callbacks sparingly

A callback can read the request and return an httpx.Response, which helps with pagination tokens or input-dependent responses. Keep callback logic much simpler than the replaced service. Recreating validation, authentication, and storage builds a second implementation that may share the same misunderstanding.

Prefer static responses for most units. Small factories work when many tests share a documented payload and need to override relevant fields. Large production responses slow reviews and hide the property under test.

Separate testing layers

RESPX verifies reactions to the contract represented by fixtures; it cannot prove that the provider still implements it. Keep a smaller contract or sandbox suite against a controlled environment when available. Separate it from fast offline units and protect credentials.

Record the source and date for representative fixtures. Update them after reviewing official provider documentation or sanitized captures, not merely to make a failing test pass. End-to-end cases should remain few because they depend on external availability.

Fail closed against accidental network

Review fixture quality

A mock response is test data and deserves review. Keep the smallest payload that preserves the behavior, but include required fields and realistic types. Name factories after the provider concept rather than a particular test. If a provider publishes an OpenAPI description, it can inform fixtures, yet generated data still needs a human check against the scenario.

When a production incident exposes an untested response, first add a sanitized regression fixture and a failing assertion, then fix the adapter. Do not copy personal information, tokens, trace identifiers, or proprietary bodies into the repository. Record why the shape is representative.

Finally, verify that router assertions execute even when application assertions fail. Fixture teardown or an explicit final assertion can detect unused routes. An unused expected route may mean the code short-circuited correctly, so make the expectation intentional instead of relying on a global setting without understanding it.

Cover pagination when the client implements it. Let the first response provide a cursor and assert that the second request sends it. Add a maximum-page case for a repeated cursor. For downloads, verify streaming cleanup and truncated responses without storing large binary fixtures.

For multipart uploads, compare the meaningful filename, media type, and bytes rather than a randomly generated boundary. If requests are signed, inject a clock and fake credential to produce deterministic signatures. These focused tests belong at the adapter boundary because they describe the external protocol clearly.

Activate a strict router around units that own HTTP calls and register every expected endpoint. Centralize clients with explicit base URLs, production timeouts, and dependency injection. An unexpected hostname then becomes visible immediately.

Cover redirects, malformed JSON, empty bodies, rate limiting, authentication failure, and partial responses according to the adapter contract. Do not test every HTTPX feature through RESPX; focus on decisions your adapter makes. Assert calls when the presence or absence of a request is itself an outcome.

Use side_effect for httpx.ConnectTimeout or a sequence of responses. This makes retry and error reporting deterministic. Do not reproduce the entire remote API in mocks; keep fixtures focused and add separate contract tests to detect genuine service changes.

The official RESPX documentation, accessed July 22, 2026, covers its fixture, route patterns, responses, and call history. Pin compatible RESPX and HTTPX versions.