示例说明
单元测试碰真实网络既慢又脆,MockTransport 在传输层截获请求:handler 函数按请求内容返回构造的响应,客户端代码零改动。handler 还能顺手捕获请求细节——URL、头、体——断言不只是响应结果,还包括请求是否符合契约。参数化用例批量覆盖服务端错误状态码。
技术要点
- 传输层替换无需改客户端代码
- handler 捕获请求细节供断言
- 参数化批量覆盖错误状态码
代码解读
首个用例的 handler 记录请求 URL 与授权头并返回发票载荷;断言验证解析结果与请求形态两个方向。参数化用例让 500、502、503 三种状态都触发 raise_for_status 的异常路径,用 pytest.raises 钉住行为。
使用建议
复杂交互序列可用 respx 库获得路由式声明。测试域名约定 .test 后缀防误连真实环境。真实边界留给少量契约测试。
代码示例
import httpx
import pytest
def fetch_invoice(base_url: str, invoice_id: str) -> dict:
with httpx.Client(base_url=base_url) as client:
resp = client.get(f'/invoices/{invoice_id}')
resp.raise_for_status()
return resp.json()
def test_fetch_invoice_parses_payload():
captured: dict = {}
def handler(request: httpx.Request) -> httpx.Response:
captured['url'] = str(request.url)
captured['auth'] = request.headers.get('Authorization')
return httpx.Response(200, json={'id': 'inv-1', 'total': 4200})
client = httpx.Client(
transport=httpx.MockTransport(handler),
base_url='https://api.example.test',
headers={'Authorization': 'Bearer test-token'},
)
with client:
resp = client.get('/invoices/inv-1')
data = resp.json()
assert data['total'] == 4200
assert captured['url'].endswith('/invoices/inv-1')
assert captured['auth'] == 'Bearer test-token'
@pytest.mark.parametrize('status', [500, 502, 503])
def test_fetch_invoice_raises_on_server_error(status):
def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(status)
client = httpx.Client(transport=httpx.MockTransport(handler))
with pytest.raises(httpx.HTTPStatusError):
client.get('https://api.example.test/invoices/inv-2')