由 MockDeck (LX AI) 提供
基于契约的 API 模拟
基于契约的模拟意味着在实现存在之前,先同意端点的形状 —— 路径、方法、请求字段、响应字段、错误情况 —— 然后从该描述生成模拟。契约是文档;模拟是可以构建的副产品。
为什么从合同开始
我在看到团队浪费数周的时间等待后端开发迟迟不能按时交付后,创建了MockDeck。对于任何新功能,我采取的第一步是写一个简短的端点描述,供前端和后端工程师共同审阅。这个描述成为合同。当合同固定后,前端可以开始针对模拟服务编码,模拟服务的行为与真实服务完全相同,而后端团队则并行地工作于实现。这样可以消除开发人员闲置的时间,他们不再盯着404或只返回成功代码的占位符。通过将工作锚定在具体的工件上,我们将反馈循环从天减少到小时,并避免了关于有效载荷形状或错误处理的假设被证明是错误的时所产生的昂贵的返工。
编写合同
编写合同时,需要捕捉端点的每一种可观察行为。我从HTTP路径和方法开始,接着记录任何身份验证方案(API密钥、Bearer令牌、互相TLS)。然后,我列出请求参数:查询字符串、头部和正文字段,并标记每个字段为必需或可选的。对于每个字段,我添加一个类型(字符串、数字、布尔值、数组、对象)和一个格式(如果需要,例如电子邮件、日期-时间、uuid)。接下来,我定义成功响应:状态代码、任何头部和有效载荷的完整形状,包括类型和格式。最后,我添加前端必须处理的错误情况:验证错误(400)、未找到(404)、未经授权(401)、速率限制(429)和服务器错误(5xx)。对于每个错误,我包括一个示例正文,显示错误消息的形状。
合同元素 | 示例值 | 注释 路径 | /v1/orders | 版本资源 方法 | POST | 创建新订单 身份验证 | Bearer令牌 | 必需头部Authorization 请求正文 - 必需 | customer_id(字符串,uuid) | 必须存在于数据库中 请求正文 - 可选 | note(字符串,最大500) | 自由形式评论 成功响应 | 201,{order_id,created_at} | Location头部与URL 验证错误 | 400,{errors:[{field:"customer_id",message:"必须是uuid"}]} | 帮助前端显示内联警告 未找到 | 404,{error:"订单未找到"} | 在GET /v1/orders/:id时发生,当id未知
我将合同保存在一个纯文本文件(YAML或JSON)中,该文件位于功能分支旁边。这使得在代码审查期间diff更改变得容易,并将工件存储在版本控制中与源代码一起。当合同更改时,diff显示了哪个字段被添加、删除或其类型被修改,从而在任何代码被编写之前,前端和后端所有者之间会进行对话。
生成模拟规范
一旦合同以简单的JSON文件形式写好,MockDeck就会读取它并产生三个文档:一个OpenAPI 3文档,一个Postman集合和一个原始路由文件(带有路径、方法和示例响应的纯JSON)。OpenAPI输出包括您定义的确切模式,以及自动生成的类似真实数据的示例,例如遵循典型模式的名称,落在预期范围内的数字和符合最小和最大长度的字符串。Postman集合可以直接导入到您用于手动测试的API客户端中,原始路由文件可以由轻量级模拟服务器(如Mirage JS或json-server)提供,零配置。
生成步骤是确定性的:给定相同的合同文件,您总是得到相同的模拟输出。这让您可以将生成的文档提交到您的仓库中,如果您想要一个静态模拟,或者您可以在开发服务器启动时运行MockDeck以保持模拟在内存中。因为模拟是从合同中派生的,您永远不需要维护两个独立的真实来源;对合同的任何更改都会在您重新生成时自动传播到模拟中。
将模拟集成到前端工作流中
在我的项目中,我添加一个启动脚本,在前端开发服务器启动之前启动模拟服务器。该脚本读取合同文件夹,运行MockDeck生成路由文件,然后启动一个node-express服务器,通过http://localhost:3001提供这些路由。前端代理配置为将所有/api请求转发到该端口,因此代码库看到一个平滑的API层,而无需对源代码进行任何更改。
- 在仓库根目录添加一个contracts目录。 - 为每个端点创建一个.yaml文件,遵循前面描述的结构。 - 在package.json中添加一个start:mock脚本,运行mockdeck build./contracts --out./mock,然后运行node./mock/server.js。 - 配置前端开发服务器(例如Vite、CRA)将/api代理到http://localhost:3001。 - 在一个终端运行npm run start:mock,在另一个终端运行npm run dev。
当合同发生变化时,您只需要重新启动模拟脚本;前端会立即拾取新的行为,因为代理转发到更新的模拟服务器。这紧密的循环鼓励开发人员将合同视为修改功能时要修改的第一个文物。
版本控制合同和处理漂移
合同就像代码一样会演变。我将每个合同文件视为与功能分支一起版本化。当需要破坏性更改(例如重命名字段或更改必需标志)时,我创建合同的新版本(例如v2/order-create.yaml),并更新OpenAPI生成器以包含指向版本化路径的servers块。模拟服务器然后同时提供两个版本,允许前端通过更改基本URL选择加入新的合同。
为了检测漂移,我每晚运行一个作业,比较线上后端的OpenAPI规范(从实际代码生成)与合同生成的规范。任何不匹配都会在我们的CI仪表板上报告为警告。这种方法已经捕获了后端开发人员在未更新合同的情况下添加新可选字段的案例,这将在生产中导致前端验证错误。
作为安全网的契约测试
契约测试验证模拟实现(MockDeck)和真实实现是否使用相同的语言。我使用Pact或Dredd等工具来运行契约测试,对模拟服务器和预发环境后端进行测试。测试套件发送契约中定义的每个请求,检查响应状态码、头部和正文模式,并记录任何偏差。
将此步骤纳入拉取请求工作流意味着,如果后端发生变化并破坏契约,构建将在到达主分支之前失败。同样,如果前端开发人员更新请求负载而不更新契约,测试将失败,因为模拟服务器将无法识别新的形状。
契约测试套件在典型的笔记本电脑上运行时间不到两秒,因此它为CI管道添加了可以忽略的开销,同时提供了很高的信心,即模拟保持对已同意的规范的忠实性。使用、或等工具可以进一步提高测试的效率。
什么这不是
这种方法不能保证后端会按时交付。模拟只能模拟行为,它不能替代实际的业务逻辑、数据库交互或外部服务调用,这些是真正实现将要执行的内容。
它不是端到端测试的100%替代品。你仍然需要验证集成系统在真实数据、性能特征和可能仅在负载下出现的错误条件下正确工作。
它不会消除团队之间清晰沟通的需要。契约是一种促进讨论的工具,但它不能替代定期的同步、设计审查或共享文档。
最后,使用契约优先的模拟不会使你的产品对规范错误免疫。如果契约本身是模糊或不完整的,模拟将忠实地实现这些缺陷,前端将基于有缺陷的假设构建。将契约视为一个随着双方反馈而演进的活文档。
参考资料
- https://owasp.org/www-project-top-10-for-large-language-model-applications - https://www.nist.gov/itl/ai-risk-management-framework
常见问题
这是一个运行的模拟服务器吗?
不。MockDeck 生成模拟 API 规范 — OpenAPI 3、Postman 或原始路由。服务它是一个单独的步骤。
它可以调用我们的真实后端吗?
不。它不代理或运行您的后端;输出是一个规范和示例响应。
它产生哪些格式?
OpenAPI 3、Postman 集合或原始路由。
为什么要在模拟中处理错误响应?
因为那是前端代码通常中断的地方。一个只返回 200 的模拟隐藏了您最需要处理的状态。
示例数据有多真实?
它是生成的,看起来像真实有效载荷,这足以在早期捕获格式和空状态错误。它不是生产数据。
它是否验证我们的现有 API 是否符合合同?
不。合同测试和验证是单独的工具。
它们与此无关。
它们与此无关。
和 在这里的作用是什么?
它们与此无关。
和 在这里的作用是什么?
它们与此无关。
参考
- OpenAPI 规范 - JSON 模式
尝试产品:MockDeck
2026-09-20 · 只使用主要来源 · 不允许虚构的流量指标 · 只用于决策支持
相关文章
这是一个运行的模拟服务器吗?
否。MockDeck 生成模拟 API 规范 —— OpenAPI 3、Postman 或原始路由。服务它是一个单独的步骤。
它可以调用我们的真实后端吗?
否。它不代理或运行您的后端;输出是一个规范和示例响应。
它支持哪些格式?
OpenAPI 3、Postman 集合或原始路由。
为什么在模拟中要处理错误响应?
因为那是前端代码通常中断的地方。一个只返回 200 的模拟隐藏了您最需要处理的状态。
示例数据有多真实?
它是生成的,看起来像真实有效载荷,这足以提前捕获格式和空状态错误。它不是生产数据。
它是否验证我们的现有 API 是否符合契约?
否。契约测试和验证是单独的工具。
仅供决策参考,不是生产 API 保证。