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.