← 中文资源

简体中文版本

HTTPX MockTransport 单元测试

用 httpx 的 MockTransport 无网络测试 HTTP 客户端:按 URL 编排响应脚本、捕获请求细节断言、参数化错误状态路径。

阅读英文原文

示例说明

单元测试碰真实网络既慢又脆,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')