我厌倦了在 pytest 中 Mock API,于是我构建了更简洁的方式。
Source: Dev.to

如果你在 Python 中编写集成测试已经有一段时间,你一定遇到过这个问题。
你的测试调用了三个外部服务。
你模拟一个端点。
然后是另一个。
再然后是另一个。
突然间,你的测试变成了 60 行,而且一半都是 patch。
此时你已经不再测试行为,
而是在维护脚手架。
我在处理服务间调用流程时反复遇到这种情况,尤其是当:
- 单个操作触发多个 HTTP 调用
- 不同的测试需要不同的响应组合
- fixture 开始变成小型框架
工具生态非常完善——responses、unittest.mock、httpx 的模拟工具——但一旦端点数量增加,可用性就会下降。
问题不在于能力,
而在于可读性。
临界点
以下是多端点模拟常见的情况:
import responses
@responses.activate
def test_checkout():
responses.add(
responses.GET,
"https://api.example.com/users/1",
json={"id": 1, "name": "Alice"},
status=200,
)
responses.add(
responses.POST,
"https://api.example.com/orders",
json={"order_id": 42},
status=201,
)
result = checkout_flow()
assert result.success
这在小范围下可以工作。但如果扩展的话:
- 每个测试的不同组合
- 条件响应
- 动态负载
- 部分 URL 匹配
- 多个外部服务
现在你的 fixture 增多。帮助函数增多。补丁遍布各处。测试变成了基础设施。
我想要的替代方案
我想要的东西是:
- 自然地运行在 pytest 中
- 将 mock 与测试逻辑保持靠近
- 使多端点流程易读
- 避免启动测试服务器
- 避免深层补丁树
于是我构建了一个名为 api‑mocker 的小工具。
理念很简单:最小的表面面积。没有笨重的 DSL。没有框架抽象。只有显式的端点声明。

示例
def test_checkout_flow(api_mocker):
api_mocker.get("/users/1").respond_with(
status=200,
json={"id": 1, "name": "Alice"},
)
api_mocker.post("/orders").respond_with(
status=201,
json={"order_id": 42},
)
result = checkout_flow()
assert result.success
- 没有装饰器。
- 没有激活上下文。
- 没有分散的补丁逻辑。
测试夹具负责每个测试的生命周期和清理。
设计原则
每个测试的隔离
Mocks 在每个测试后自动重置。没有共享状态泄漏。
明确的失败
- 如果未调用预期的端点,测试失败。
- 如果调用了意外的端点,测试失败。
沉默的成功会隐藏集成问题。
轻量级拦截
没有嵌入式服务器。没有进程开销。拦截发生在请求层。

此方法最适用的场景
- 微服务架构
- 调用多个第三方 API 的服务
- 支付或认证流程
- 编排式后端
只要单个流程涉及两个或多个 HTTP 集成的任何地方。
我正在探索的未解之问
Mocking 库始终在简洁性与灵活性之间保持张力。我正在积极思考的几个方向:
- 异步客户端处理
- 流式响应
- 在何种情况下应让 Mocking 让位于契约测试
- 在大型测试套件中检测过度 Mock
这些权衡会影响长期的测试质量。

为什么我要分享这个
Mocking 策略直接影响代码库的健康。
可读的测试具有可扩展性,Fixture 丛林则没有。
如果你在 Python 中处理过混乱的多端点集成测试,我真诚地想听听:
- 哪些做得好
- 哪些出现了问题
- 何时转向了契约测试
项目链接(如果你想了解实现细节):
- PyPI:
- GitHub:
