httpx mock: MockTransport for tests (tested)

Pass transport=httpx.MockTransport(handler) to httpx.Client or AsyncClient; handler(request) returns httpx.Response(200, json=...) or raises an httpx exception. No network, no extra library.

httpx has a built-in way to fake HTTP in tests: httpx.MockTransport(handler). The handler receives each httpx.Request and returns an httpx.Response; no network or extra library is needed. Measured with httpx 0.28.1 on Python 3.13.5.

1. httpx mock transport: fake responses by path

import httpx

def handler(request: httpx.Request) -> httpx.Response:
    if request.url.path == "/users/1":
        return httpx.Response(200, json={"id": 1, "name": "Ada"})
    return httpx.Response(404, json={"error": "not found"})

client = httpx.Client(transport=httpx.MockTransport(handler), base_url="https://api.example.com")
print(client.get("/users/1").json())
print(client.get("/users/2").status_code)

Output:

{'id': 1, 'name': 'Ada'}
404

Inject the client (or its transport) into the code under test, so tests can pass a mocked client and production uses a real one.

2. Assert what your code sent

import httpx

seen = []
def handler(request):
    seen.append((request.method, str(request.url), request.headers.get("authorization"), request.content))
    return httpx.Response(201)

client = httpx.Client(transport=httpx.MockTransport(handler), headers={"Authorization": "Bearer t"})
client.post("https://api.example.com/items", json={"a": 1})
print(seen)

Output:

[('POST', 'https://api.example.com/items', 'Bearer t', b'{"a":1}')]

The handler sees the final request: method, full URL, merged client headers and the encoded body.

3. Simulate network errors, and a raise_for_status gotcha

import httpx

def handler(request):
    raise httpx.ConnectTimeout("simulated", request=request)

client = httpx.Client(transport=httpx.MockTransport(handler))
try:
    client.get("https://api.example.com/")
except httpx.ConnectTimeout as e:
    print("ConnectTimeout:", e)

r = httpx.Response(500)
try:
    r.raise_for_status()
except RuntimeError as e:
    print("RuntimeError:", e)

Output:

ConnectTimeout: simulated
RuntimeError: Cannot call `raise_for_status` as the request instance has not been set on this response.

Raise an httpx exception from the handler to test timeout and retry paths. A bare httpx.Response(500) built outside a client has no request attached, so raise_for_status() raises RuntimeError instead of HTTPStatusError; responses returned through a client (as in block 1) have the request set.

4. httpx mock with AsyncClient

import asyncio, httpx

async def handler(request):
    return httpx.Response(200, json={"async": True})

async def main():
    async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
        r = await client.get("https://api.example.com/")
        print(r.json())

asyncio.run(main())

Output:

{'async': True}

For AsyncClient the handler can be an async def. Third-party libraries such as respx and pytest-httpx add route matching on top of the same idea.

Related: pytest mock patch not working, httpx raise_for_status, httpx retries, httpx vs requests differences.

Frequently asked questions

How do I mock httpx in tests?

Create httpx.Client(transport=httpx.MockTransport(handler)) where handler(request) returns an httpx.Response.

Can MockTransport simulate a timeout?

Yes: raise httpx.ConnectTimeout('msg', request=request) (or another httpx exception) from the handler.

Why does raise_for_status raise RuntimeError on a mock response?

An httpx.Response built by hand has no request attached; return it through a client (MockTransport) so the request is set.

Written with AI assistance. Measured on 2026-10-23 with httpx 0.28.1 on Python 3.13.5. Each block was run verbatim and the output pasted unedited. PlainHeaders is our extension; httpx is free.