开始写代码。首先是源适配器框架(依赖注入 httpx client 以便测试用 MockTransport 全覆盖)

HTTPX MockTransport 完全指南:优雅地 Mock HTTP 请求

在 Python 项目中,我们经常需要测试调用第三方 API 的代码,例如:

  • 调用 OpenAI API
  • 调用 GitHub API
  • 调用支付接口
  • 调用天气接口
  • 微服务之间的 HTTP 调用

如果测试过程中真的去访问互联网,会带来很多问题:

  • 网络不稳定导致测试失败
  • API 有调用次数限制
  • 请求速度慢
  • 需要配置各种 Token
  • 测试数据不可控

因此,Mock HTTP 请求几乎是所有大型 Python 项目的标准实践。

而对于使用 HTTPX 的项目来说,官方提供了一个非常优雅的解决方案:

httpx.MockTransport

本文将全面介绍它的使用方法、工作原理以及最佳实践。


什么是 MockTransport

MockTransport 是 HTTPX 官方提供的一种 Transport(传输层)实现

它不会真正发送 HTTP 请求,而是把请求交给你自己编写的函数处理。

整个流程如下:

Client.get()

        │
        ▼

MockTransport

        │
        ▼

你的 handler()

        │
        ▼

返回 Response

        │
        ▼

Client 收到 Response

例如:

client.get("https://api.github.com/users/octocat")

正常情况下:

Client
      │
      ▼
Internet
      │
      ▼
GitHub API
      │
      ▼
Response

使用 MockTransport 后:

Client
      │
      ▼
MockTransport
      │
      ▼
handler(request)
      │
      ▼
Response(status_code=200)

整个过程中:

  • 不访问互联网
  • 不需要 DNS
  • 不需要 Token
  • 不需要服务器

为什么 HTTPX 要设计 Transport

HTTPX 的底层采用了 Transport 抽象层

可以理解为:

Client

↓

Transport

↓

TCP Socket

默认情况下:

client = httpx.Client()

实际上使用的是:

HTTPTransport

而:

client = httpx.Client(
    transport=MockTransport(...)
)

则替换了真正的网络层。

所以:

你的业务代码完全不知道请求有没有真正发出去。

这就是依赖抽象(Dependency Inversion)的思想。


最简单的示例

例如:

import httpx


def handler(request):
    return httpx.Response(
        status_code=200,
        json={"message": "hello"}
    )


transport = httpx.MockTransport(handler)

client = httpx.Client(transport=transport)

response = client.get("https://example.com")

print(response.json())

输出:

{'message': 'hello'}

整个过程没有任何网络请求。


handler 的工作原理

handler 接收一个:

httpx.Request

例如:

def handler(request):
    print(request.method)
    print(request.url)

    return httpx.Response(200)

运行:

client.get("https://example.com/test")

输出:

GET
https://example.com/test

说明:

请求对象是真实构造出来的。

你可以检查:

  • URL
  • Method
  • Header
  • Query
  • Cookie
  • Body

返回 JSON

最常见的情况就是返回 JSON。

def handler(request):
    return httpx.Response(
        status_code=200,
        json={
            "id": 1,
            "name": "Alice"
        }
    )

客户端:

response = client.get("/users/1")

print(response.json())

输出:

{
    "id": 1,
    "name": "Alice"
}

返回文本

def handler(request):
    return httpx.Response(
        status_code=200,
        text="Hello World"
    )

读取:

print(response.text)

返回 HTML

def handler(request):
    return httpx.Response(
        200,
        headers={
            "Content-Type": "text/html"
        },
        text="<h1>Hello</h1>"
    )

返回图片

例如:

def handler(request):
    return httpx.Response(
        200,
        content=b"\x89PNG..."
    )

读取:

response.content

模拟不同状态码

例如:

404:

def handler(request):
    return httpx.Response(404)

500:

def handler(request):
    return httpx.Response(500)

401:

def handler(request):
    return httpx.Response(401)

429:

def handler(request):
    return httpx.Response(429)

这样就可以测试各种异常处理逻辑。


根据 URL 返回不同结果

例如:

def handler(request):

    if request.url.path == "/users":
        return httpx.Response(
            200,
            json=[{"id": 1}]
        )

    if request.url.path == "/login":
        return httpx.Response(
            200,
            json={"token": "abc"}
        )

    return httpx.Response(404)

这样一个 MockTransport 就可以模拟整个 REST API。


根据请求方法返回不同结果

def handler(request):

    if request.method == "GET":
        return httpx.Response(
            200,
            json={"action": "read"}
        )

    if request.method == "POST":
        return httpx.Response(
            201,
            json={"action": "create"}
        )

    return httpx.Response(405)

检查请求 Header

例如:

def handler(request):

    token = request.headers.get("Authorization")

    if token != "Bearer abc":
        return httpx.Response(401)

    return httpx.Response(200)

测试认证逻辑非常方便。


检查 Query 参数

def handler(request):

    page = request.url.params.get("page")

    return httpx.Response(
        200,
        json={
            "page": page
        }
    )

调用:

client.get("/users?page=2")

返回:

{
    "page": "2"
}

检查 POST Body

JSON:

import json

def handler(request):

    body = json.loads(request.content)

    return httpx.Response(
        200,
        json=body
    )

调用:

client.post(
    "/users",
    json={
        "name": "Tom"
    }
)

返回:

{
    "name": "Tom"
}

模拟接口异常

例如模拟服务器错误:

def handler(request):
    return httpx.Response(500)

或者模拟超时等网络异常:

def handler(request):
    raise httpx.ConnectTimeout("Connection timed out")

业务代码便可以验证重试、降级或异常处理是否符合预期。


AsyncClient 中使用 MockTransport

异步客户端同样支持。

import httpx
import asyncio


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


transport = httpx.MockTransport(handler)


async def main():
    async with httpx.AsyncClient(
        transport=transport
    ) as client:

        r = await client.get("/")

        print(r.json())


asyncio.run(main())

接口与同步版本保持一致。


在 pytest 中使用

例如:

import httpx


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


def test_api():

    client = httpx.Client(
        transport=httpx.MockTransport(handler)
    )

    response = client.get("/")

    assert response.status_code == 200

    assert response.json()["success"]

无需启动任何 Mock Server,即可完成测试。


MockTransport 与 unittest.mock 的区别

很多开发者会直接使用 unittest.mock.patch() 来 Mock HTTP 请求,但两者关注点不同。

对比项 MockTransport unittest.mock
Mock 层级 HTTP 传输层 任意 Python 对象
是否保留 HTTP 请求对象 ✅ 是 ❌ 通常需要自行构造
是否模拟完整 HTTP 行为 ✅ 是 ❌ 取决于 Mock 内容
是否依赖 HTTPX ✅ 是 ❌ 通用
更适合 HTTP 客户端测试 单元测试、依赖替换

如果你的代码使用 HTTPX 发起请求,MockTransport 往往更自然,因为它保持了 HTTP 请求/响应模型。


MockTransport 与 RESPX 的区别

除了官方提供的 MockTransport,社区还有一个广受欢迎的库 RESPX,专门用于 HTTPX 请求 Mock。

对比项 MockTransport RESPX
来源 HTTPX 官方 第三方库
配置方式 编写 handler 声明式路由
学习成本 较低 中等
适合复杂接口 Mock 一般 更强
是否需要额外依赖

对于简单或中等规模的测试,MockTransport 已经足够;如果需要大量接口、复杂匹配规则或断言请求调用次数,RESPX 会更方便。


最佳实践

1. 不要在 handler 中写过于复杂的业务逻辑

handler 的职责应该是模拟接口行为,而不是复制真实服务的全部实现,否则测试本身会变得难以维护。


2. 一个测试只关注一种场景

例如分别编写:

  • 成功响应
  • 404
  • 401
  • 500
  • 超时
  • 返回非法 JSON

这样每个测试的目的都很明确。


3. 将 Mock 数据集中管理

对于大型项目,可以把常见响应抽取成独立模块,例如:

tests/
    mocks/
        github.py
        openai.py
        payment.py

避免在多个测试文件中重复构造相同的响应。


4. 尽量通过依赖注入传递 Client

不要在业务代码内部直接创建 httpx.Client(),而是允许传入客户端实例:

def fetch_user(user_id: int, client: httpx.Client) -> dict:
    response = client.get(f"/users/{user_id}")
    response.raise_for_status()
    return response.json()

这样在测试时即可注入使用 MockTransport 的客户端,而生产环境则传入真实客户端,代码无需修改。


总结

httpx.MockTransport 是 HTTPX 官方提供的轻量级 HTTP Mock 方案,通过替换底层传输层,让应用代码无需真正访问网络即可获得可控的响应。相比直接使用 unittest.mock 替换方法,它保留了完整的 HTTP 请求与响应模型,更贴近真实运行环境;相比第三方 Mock Server,它无需启动额外服务,执行速度更快,维护成本也更低。

对于使用 HTTPX 的项目,推荐将 MockTransportpytest、依赖注入相结合,用于覆盖成功、异常、超时、认证失败等各种场景,从而构建快速、稳定且可重复执行的自动化测试体系。在大多数单元测试和轻量级集成测试中,它已经能够满足绝大多数需求,是 HTTPX 官方推荐且符合企业最佳实践的 Mock 方式。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐