关于 service-virtualization
一项帮助测试团队为依赖选择隔离策略的技能,涵盖 HTTP 桩、录制回放、故障注入和临时真实服务。它强调在 HTTP 层设置桩,并检查测试是否拦截了未匹配的请求。
主要功能
- 01按依赖和测试层级选择隔离方式
- 02在 HTTP 层而非 SDK 方法设桩
- 03使用 MSW 和 WireMock 模拟 HTTP
- 04通过 Testcontainers 启动真实服务
- 05用 Toxiproxy 注入网络故障
- 06录制并回放 API 流量
适用场景
- 01无需调用线上服务即可测试第三方 API 错误
- 02使用临时数据库运行集成测试
- 03验证超时和连接重置的处理
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ petrkindlmann/ qa-skills --skill service-virtualization安装后在 Claude Code 中输入/service-virtualization确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/ci.mdmsw.mdrecord-replay.mdtestcontainers.mdtoxiproxy.mdwiremock.md详细介绍
快速导航
| 情况 | 前往 |
|---|---|
| Node/浏览器测试调用外部 HTTP API | MSW → references/msw.md |
| 多语言 CI、复杂匹配,或需要独立的存根服务 | WireMock → references/wiremock.md |
| 依赖是数据库 / 缓存 / 队列 | Testcontainers → references/testcontainers.md |
| 测试超时、延迟、连接重置 | Toxiproxy → references/toxiproxy.md |
| 从真实 API 初始化存根,或建立多步骤基线 | 录制回放 → references/record-replay.md |
| 将上述任一工具接入 GitHub Actions | references/ci.md |
| 不确定该选哪种 | 决策树(下文) |
调研问题
先检查 .agents/qa-project-context.md——如果存在,请使用其中的信息,遵循现有的模拟约定,并跳过其中已解答的问题。然后:
- 系统会调用多少个外部依赖?哪些依赖在测试中很难处理? 有速率限制、速度慢、不稳定或需要付费的依赖,是最优先考虑虚拟化的对象;列出支付、电子邮件、身份验证和第三方数据源。
- 哪些测试层级需要隔离? 单元测试需要快速的进程内模拟;集成测试需要 HTTP 层存根;端到端测试需要真实服务或容器化服务——测试层级决定策略。
- 提供方是否提供官方测试模式或沙箱? Stripe 测试模式、Twilio 测试凭证等,在保真度方面都胜过自建存根——有此类选项时优先使用。
- 你是否与这些依赖建立了契约? 如果有,契约测试可以让存根保持同步;请参阅
contract-testing。如果没有,模拟非自有对象是一项已知风险(核心原则 3)。 - CI 中是否有 Docker? 没有 Docker 时应选择 MSW(进程内);有 Docker 则可以使用 WireMock、Testcontainers 和 Toxiproxy。
核心原则
1. 根据所需的置信度选择隔离级别。 单元测试可以积极地使用模拟——它们测试的是内部逻辑。集成测试应使用逼真的存根或真实服务,因为它们测试的是边界。端到端测试应运行尽可能接近生产环境且仍然可靠的服务。
2. 真实服务只要速度快、免费且可靠,就胜过伪造服务。 本地 PostgreSQL 容器比伪造服务更让人放心,成本也很低——应优先使用。只有对速度慢、不可靠或需要付费的依赖,才使用伪造服务。
3. 没有契约时,绝不要模拟非自有对象——并优先使用提供方的测试模式。 如果你为 Stripe 设置了存根,而 Stripe 更改了响应结构,你的测试仍会通过,生产环境却会出问题。按优先顺序:使用提供方的官方测试模式 / 沙箱(Stripe 测试模式、Twilio 测试凭证)→ 使用通过契约测试验证漂移的 HTTP 存根 → 仅在没有更好选择时使用裸存根。请参阅 contract-testing。
4. 存根必须逼真地失败。 始终返回 200 的存根无法测试错误处理。每个存根都需要错误变体:429 速率限制、500 错误、超时、格式错误的响应正文。
5. 在测试和工具之间只保留一层抽象。 将 MSW 处理器、WireMock 存根和 Testcontainers 配置封装在统一接口后,这样切换工具就不必重写测试。
决策框架
何时使用各隔离策略
| 策略 | 速度 | 保真度 | 复杂度 | 最适合 |
|---|---|---|---|---|
| 进程内模拟 | 最快 | 最低 | 极低 | 单元测试、隔离内部模块 |
| HTTP 桩(MSW) | 快 | 中等 | 低 | 调用外部 API 的前端/Node 测试 |
| HTTP 桩(WireMock) | 快 | 中高 | 中等 | 与语言无关的场景、复杂匹配规则 |
| 录制回放 | 首次运行后很快 | 初始较高,之后会衰减 | 中等 | 快速根据真实 API 搭建桩 |
| 服务伪实现 | 中等 | 高 | 高 | 有状态依赖(内存数据库、伪认证) |
| 临时真实服务(Testcontainers) | 较慢 | 最高 | 中等 | 数据库、消息队列、缓存 |
| 共享真实服务 | 慢 | 生产级 | 低(搭建成本) | 预发布验证、部署前最终检查 |
决策树
Is the dependency internal to your codebase?
├─ Yes → In-process mock (vi.mock / jest.mock / monkeypatch)
└─ No → Is it a database, cache, or message queue?
├─ Yes → Testcontainers (ephemeral real instance)
└─ No → Is it a third-party HTTP API?
├─ Yes → Does the provider offer a test/sandbox mode?
│ ├─ Yes → Use sandbox in staging, MSW/WireMock in CI
│ └─ No → MSW or WireMock + contract test for drift detection
└─ No → Is it an internal microservice?
├─ Yes → Contract test (Pact) + stub for consumer tests
└─ No → Evaluate case by case
工具
进程内 Node/浏览器测试默认选择 MSW;跨语言 CI 或独立桩服务器选择 WireMock;以 OpenAPI 规范作为契约时选择 Prism;开发时探索性模拟则选择 Mockoon。各工具的大段代码位于 references/ 中,下面提供相关指引。
MSW(Mock Service Worker)
MSW 可在浏览器或 Node 中拦截 HTTP。本参考针对固定的 2.15.0 API;复制代码前请检查已安装的主版本,因为 MSW 3 会更改导入项和选项。
在 HTTP 层搭建桩(POST /v1/payment_intents),不要模拟 SDK 方法——这样可让测试与 SDK 版本解耦。
对于 MSW 2,使用 setupServer(...).listen({ onUnhandledRequest: "error" }),拒绝没有匹配处理程序的拦截请求。验证预期的模拟响应,并验证一次有意触发的未处理请求拒绝。显式透传、未捕获的传输,或吞掉该拒绝的应用代码,都需要单独检查;测试套件通过或日志搜索结果为空,并不能保证没有出站请求。
将选择启用的错误处理程序放在匹配的成功处理程序之前。在测试之间重新构建有状态工厂:单独调用 resetHandlers() 不会清除工厂捕获的映射。有关支付处理程序、显式本地回退、状态重置、包含超时/重试用例的可执行示例客户端,以及基于 Bearer 令牌的有状态认证流程(创建/验证/刷新/通过 http.delete 撤销),请参见 references/msw.md。
WireMock
与语言无关的 HTTP 桩服务器(当前稳定版为 3.13.2——截至 2026 年年中,4.0 仅为 Beta 版;CI 请继续使用 3.x)。可独立运行,也可作为 Docker 容器运行。最适合多语言环境或复杂匹配规则。
使用基于优先级的映射处理错误场景:只有测试明确要求时,匹配选择性启用请求头(X-Test-Scenario: rate-limit)并返回 429 的 priority: 1 映射才会覆盖默认的正常路径映射。WireMock 还提供管理 API,可用于以编程方式创建桩(POST /__admin/mappings)、验证(POST /__admin/requests/count)和重置(POST /__admin/mappings/reset)。
有关 Docker 设置、使用响应模板的分页映射、优先级错误映射 JSON,以及连接到 contract-testing 的差异检测接口(回放 Pact 或通过 Prism 根据 OpenAPI 规范验证桩),请参见 references/wiremock.md。
其他 HTTP 模拟服务器
| 工具 | 优势 | 使用场景 |
|---|---|---|
| Mockoon | 桌面界面 + CLI;导入 OpenAPI;基于规则的响应;轻量 | 开发时模拟,以及在 CI 中快速创建 CLI 模拟 |
| Hoverfly | 捕获回放(录制真实流量并确定性地回放);捕获/模拟/修改/合成模式 | 从真实依赖迁移到模拟——录制一次,永久回放 |
| Prism | OpenAPI 驱动的模拟服务器(Stoplight);验证请求 + 根据规范生成响应 | 优先采用 OpenAPI 且已发布规范的项目 |
| MockServer | 基于 Java;支持丰富的期望匹配;多协议 | 已在使用 MockServer 的 JVM 团队 |
Testcontainers
在 Docker 中启动真实服务以进行集成测试——测试套件运行前启动容器,结束后销毁容器。使用 @testcontainers/postgresql 11.x 及相关工具。端口是随机分配的;始终通过 getMappedPort() / getConnectionUri() 读取端口,绝不要硬编码 5432/6379。
请参阅 references/testcontainers.md,了解使用等待策略并行启动 PostgreSQL + Redis + Elasticsearch、Vitest 的 globalSetup 配置(process.env.DATABASE_URL、testTimeout: 30_000),以及更新过时镜像标签的说明(elasticsearch:8.12.0 已落后于 Elastic 9.x)。
Toxiproxy(故障注入)
ghcr.io/shopify/toxiproxy:2.12.0 作为 TCP 代理置于应用与依赖项之间,可注入延迟、带宽限制和连接重置。让应用连接到代理端口,而非真实服务。务必在 afterEach 中移除 toxics,否则它们会影响后续测试。
请参阅 references/toxiproxy.md,了解 compose 端口映射、createProxy(name, listen, upstream) 配置(包括 compose 的 15432/16379 代理端口如何映射到真实上游服务)、每次调用都检查 response.ok 的辅助函数,以及延迟 + 连接重置用法示例。若要开展大范围的韧性测试或游戏日演练,请改用 chaos-engineering。
录制-回放
录制-回放会捕获真实 API 响应,并以确定性的方式重放——适合用于搭建存根,也适合作为多步骤交互的回归基线。请使用录制-回放库(Hoverfly、Polly.JS 或 VCR 风格的录制文件)来实现,而不要自行编写录制器。
动态数据(时间戳、UUID)、有状态序列和时间推移都会导致它失效——录制内容可能在几周内就会过时。务必记录 recordedAt,并在录制内容超过 30 天时让测试失败,以强制重新录制。
请参阅 references/record-replay.md,了解录制文件格式、assertFresh() 的 30 天过期检查,以及驱动多步骤流程(创建订单 → 添加商品 → 应用优惠券 → 结账)的回放测试框架。
CI 集成
MSW 不需要单独的存根服务器。在 CI 中设置 onUnhandledRequest: "error",断言预期的模拟响应已被观察到,并执行一个刻意未处理的请求来验证防护机制。记录意外绕过的请求;若要求完全禁止出站网络流量,请使用 CI 网络控制。WireMock 和 Testcontainers 需要 Docker。
两种不兼容的端口模型并存,不得在同一测试套件中混用:docker-compose 模型会发布固定端口(硬编码 DATABASE_URL=...localhost:5432... 可行);Testcontainers 模型使用通过 getMappedPort() 读取并注入 process.env 的随机端口。每个测试套件只能选用一种模型。
请参阅 references/ci.md,了解 MSW 步骤、docker-compose GitHub Actions 任务(up -d --wait --wait-timeout 120、if: always() 清理),以及按约束条件分类的工具表。
反模式
1. 什么都模拟
如果每个依赖项都被模拟,你的测试验证的只是模拟本身是否有效,而非系统是否有效。数据库和缓存请使用真实服务(通过 Testcontainers);仅对外部 HTTP API 使用存根。
2. 不同测试中的模拟行为不一致
一个测试将 Stripe 存根为 { id: "pi_123" },另一个则存根为 { paymentIntentId: "pi_123" }——现在你有了两个互相冲突的现实版本。请集中管理处理程序,并在整个测试套件中复用同一种响应结构(参见 references/msw.md 中的共享结构)。
3. API 变更后未更新存根
你的映射表明 Stripe 返回 { amount: 1000 },但真实 API 现在返回 { amount: 1000, currency: "usd" }。测试通过了,生产环境却失败。请使用契约测试检测偏差——参见 contract-testing 和 references/wiremock.md 中的偏差检测接口。
4. 在错误的层进行存根
模拟 stripe.paymentIntents.create(SDK 方法)会使测试与 SDK 版本耦合。请在 HTTP 层设置存根(POST /v1/payment_intents),这样无论使用哪个 HTTP 客户端或 SDK 版本,测试都能正常运行。
5. 未覆盖错误场景
始终返回 200 的桩不会触发重试逻辑、超时处理、限流退避或错误解析。每个桩都需要对应的错误变体。
6. 共享的长期运行模拟服务
多个 CI 作业共同访问同一个 WireMock 实例会造成耦合和状态泄漏。每次测试运行都要启动自己的隔离桩服务。
7. 未设置过期时间的录制回放
六个月前的录制反映的是一个已不存在的 API。添加 recordedAt 时间戳,并在录制超过 30 天时让测试失败,强制重新录制(参见 references/record-replay.md)。
8. 成功处理器遮蔽错误场景
MSW 会在遇到第一个返回响应的匹配处理器时停止。放在创建支付处理器之后的限流处理器永远无法返回 429。将选择性启用的错误处理器放在前面,然后针对同一端点验证错误请求和普通成功请求。
9. 重置处理器但保留夹具状态
server.resetHandlers() 会移除运行时覆盖项,但现有闭包仍持有支付或会话映射。每次测试之间都要重新创建各个有状态处理器工厂,并验证之前存在的记录之后已不存在。在夹具中使用唯一 ID,而不是在冻结时钟下会发生冲突的时间戳。
验证
验证实际代码和观察到的请求,从最小检查开始:
# In this skill repository: execute the actual MSW reference code fences.
node --test --test-isolation=none tests/examples/msw.test.mjs
回归测试必须观察到正常的创建/获取/确认流程、选择性启用的 429、处理器已收到请求后客户端发生超时、次数受限的 503 重试、重置后的新状态,以及在回退之前被拒绝的刻意未处理请求。将限流处理器移到成功处理器之后,或保留旧状态,都必须使相应案例失败。
对于使用者项目,在 CI 设置下运行其实际集成命令,并验证相同信号。日志中未出现提供方主机名,只能证明搜索结果中没有对应行;模拟请求本身也可能记录提供方 URL。请分别检查匹配/未处理/绕过请求的证据和网络限制情况。对于容器夹具,请验证其正常启动并确保完成清理。说明实际测试过哪些传输方式和场景;此配方测试不验证真实提供方契约。
完成标准
- 已为每个外部依赖确定并记录隔离策略(哪些使用 MSW/WireMock 桩,哪些使用 Testcontainers,哪些使用提供方的沙盒模式)。
- 桩覆盖所有关键外部依赖,且每个依赖至少包含一条错误路径(4xx/5xx、超时或限流)。
- 桩和映射文件与测试代码一同在同一个代码仓库中进行版本控制。
- 在已安装框架的未处理请求保护机制启用时,测试套件运行通过;观察到预期的模拟请求,且刻意未处理的请求证明保护机制会拒绝回退。未预期的绕过均已说明;零出网声明有网络证据支持。
- 有状态夹具和运行时覆盖项会在测试之间重置;重置前创建的记录之后已不存在,之前的错误覆盖项也不再影响下一项测试。
- 任何录制回放基线都带有
recordedAt时间戳和 30 天过期检查,过期时会使测试失败。
参考文件(位于 references/ 中)
- msw.md — 有序的支付/错误处理程序、带显式本地回退开关和状态重置的 Vitest 设置、可执行的超时/重试示例,以及创建/验证/刷新/撤销身份验证。
- wiremock.md — Docker 设置、响应模板化和分页映射、基于优先级的错误映射、管理 API,以及契约漂移衔接点(Pact/Prism)。
- testcontainers.md — 并行启动 PostgreSQL/Redis/Elasticsearch、
globalSetup接线,以及镜像标签刷新说明。 - toxiproxy.md — compose 端口、
createProxy上游接线、带response.ok检查的辅助函数,以及延迟 + 重置用法示例。 - record-replay.md — 工具选择、录制带格式、30 天
assertFresh检查,以及多步骤回放测试框架。 - ci.md — MSW(零基础设施)、docker-compose GitHub Actions 作业、两种端口模型,以及按约束条件划分的工具表。
相关技能
- contract-testing — 使用 Pact/broker 进行消费者驱动的契约验证;若要证明桩与真实提供方匹配,而不只是检测它与共享架构之间的漂移,请参阅此项。
- test-environments — 完整的 Docker Compose 环境策略、预览环境和种子数据;若要搭建环境,而非隔离单个依赖项,请参阅此项。
- chaos-engineering — 广泛的故障注入演练、演练日和爆炸半径限制;若目标是韧性本身,而非让单个依赖项在测试中表现异常,请参阅此项。
- api-testing — REST/GraphQL 测试模式、架构验证和身份验证流程测试。
- test-data-management — 用于设置桩状态的工厂模式和数据播种。