SuperAPI AI 工具集

server-mocha-to-vitest

测试与质量

查看项目

更新于 10-17 10:30

技能介绍

用途

该技能用于将 Cypress 仓库中 @packages/server 的单元测试规范从 mocha 迁移到 vitest。迁移范围严格限定在 packages/server/test/unit 目录;集成测试与性能测试套件继续使用 mocha。技能的核心目标是保证迁移后的测试在 vitest 下运行,且测试所验证的内容与 mocha 原版完全一致。

主要能力

  • 文件命名与运行器约定:*_spec.ts 通过 test/scripts/run.js 在 mocha 上运行;*.spec.ts 通过 packages/server/vitest.config.ts 在 vitest 上运行。yarn test-unit 会同时运行两者。
  • 完整移植要求:使用 git mv foo_spec.ts foo.spec.ts 重命名并重写文件,完成后原 mocha 文件必须不存在。不得留下半转换文件:要么原文件中的每个 it 都被移植,要么完全不触碰该文件。
  • 测试辅助工具替换:新文件必须显式从 vitest 导入,不得使用 spec_helper、mockery、proxyquire、sinon、chai 及其插件、snap-shot-it、mocked-env。nock、supertest、express、lodash、bluebird 等若原文件用于实际工作,则保留。
  • 禁止削弱测试:不得添加 .skip,不得删除或放宽断言,不得使用 vitest -u 让快照通过。若测试无法变绿,应保留失败状态并在 PR 中说明原因。
  • 禁止触碰特定文件:不得修改 .circleci/、vitest.config.ts 或 cloud/environment_spec.ts(后者在四个 CI 工作流中被引用,需单独的人工 PR 迁移)。
  • 网络限制:测试不得连接非回环地址,必须在沙箱主机上通过。

使用方式

内联 spec_helper 钩子

spec_helper.js 不会随迁移移植。由于 mocha 规范可能隐式依赖其他规范安装的根钩子,而 vitest 中每个文件独立运行,因此需要阅读 spec_helper.js,仅内联当前文件实际依赖的钩子。技能给出了常见钩子的内联对照表,例如:

  • setCtx(makeDataContext({})) 在 before 中,getCtx()._reset() 与 clearCtx() 在 after 中,仅当 SUT 调用 getCtx() 时使用。
  • nock.disableNetConnect() 与 nock.enableNetConnect(/localhost/) 在 before 中,nock.cleanAll() 与 nock.enableNetConnect() 在 after 中,任何使用 nock 的规范都需要。
  • cache.remove() 在每个测试前,用于触碰 lib/cache 的规范。
  • GracefulExit.resetForTesting() 用于启动服务器的规范,且需先设置 globalThis.IS_TEST = true。
  • process.env 克隆与恢复,或使用 vi.stubEnv 与 vi.unstubAllEnvs()。
  • mockElectron(mockery) 替换为 vi.mock('electron', ...),仅当 SUT 的导入图触及 electron 时。
  • sinon.restore() 替换为 vi.restoreAllMocks(),但需注意其会清除 vi.fn().mockReturnValue(...) 的行为。

Mock 验证与路径匹配

每个 mock 都必须证明其确实拦截了目标模块。可临时在工厂函数中添加 throw 或调用计数,运行规范后移除。仅凭测试通过不能证明 mock 生效。vi.mock 的说明符必须是导入方书写的字符串(相对路径从当前规范位置解析),例如:

vi.mock('../../../../lib/cloud/network/fetch')

若路径解析到别处,mock 会被忽略,真实模块被加载。

CJS require 拦截

SUT 内部的裸 require() 永远不会被 vi.mock 拦截。需要像 mockery 那样填充 CJS 缓存:

const requireCjs = createRequire(import.meta.url)
const morganPath = requireCjs.resolve('morgan')

requireCjs.cache[morganPath] = { exports: morganMockFactory } as NodeModule
// afterAll / afterEach: delete requireCjs.cache[morganPath]

exports 值应恰好是 SUT 的 require() 返回的内容。

部分 mock Node 内置模块

必须同时覆盖 default 导出,否则使用 import fs from 'fs' 的 SUT 会读取到真实函数:

vi.mock('fs', async (importOriginal) => {
  const actual = await importOriginal<typeof import('fs')>()
  return { ...actual, createReadStream: stub, default: { ...actual, createReadStream: stub } }
})

断言语义转换

  • chai 的 deep.equal 对应 toStrictEqual,而非 toEqual。toEqual 会忽略值为 undefined 的键,导致断言过宽。
  • sinon 的 calledWith 是前缀匹配;vitest 的 toHaveBeenCalledWith 要求精确参数数量。需断言完整参数列表或按前导参数过滤 mock.calls。
  • sinon.stub().withArgs(x) 返回过滤后的 stub;若原代码直接赋值该返回值,则过滤从未生效,stub 会响应所有调用。移植前需确认 mocha stub 的实际行为。
  • vi.restoreAllMocks() 会清除 vi.fn().mockReturnValue(...) 的实现;共享夹具应使用 vi.fn(() => value) 以在 restore 后保留实现。

Bluebird 与计时器

若 SUT 在 mock 返回值上链式调用 Bluebird 方法(如 .return()、.spread()、.tap()),mock 应返回 Bluebird.resolve(...) 而非原生 Promise。使用 vi.useFakeTimers() / vi.useRealTimers(),clock.tickAsync(n) 变为 await vi.advanceTimersByTimeAsync(n)。对于 nock 的 delayConnection,建议使用 vi.runAllTimersAsync() 分轮执行或小步推进时钟。

GraphQL 与 schema 重复构建

导入 lib/server-base、lib/routes 或 lib/makeDataContext 会在 worker 中第二次构建 GraphQL schema,graphql 会拒绝重复 realm。需要 mock @packages/data-context/graphql/makeGraphQLServer。若同时调用 makeDataContext,还需使用仅包含 SUT 触及成员的最小 DataContext 对象。

源码修改

仅允许明显可测性改进:添加保留行为的可选默认参数、导出已存在的函数、将内联表达式提取为同一文件中的命名纯函数。不允许改变控制流、改变调用方依赖的签名、每批触碰超过一个 lib/ 文件,或无法用一句话说明理由的修改。每次源码修改需在 PR 正文中单独标题说明。

适用场景

适用于 Cypress 仓库中 packages/server/test/unit 下 mocha 单元测试向 vitest 的迁移工作。需要开发者已经熟悉 sinon.stub 到 vi.fn、context 到 describe 等基础 API 映射。

限制

  • 迁移范围仅限 packages/server/test/unit;集成与性能套件不迁移。
  • 不得修改 .circleci/、vitest.config.ts 或 cloud/environment_spec.ts。
  • 不得削弱测试;无法变绿的测试保留失败并在 PR 中说明。
  • 测试不得连接非回环地址。
  • 技能不提供收费、平台或效果相关信息。

验证

yarn workspace @packages/server test-unit-vitest -- test/unit/<dir>/foo.spec.ts
yarn workspace @packages/server test-unit          # both runners, from a real terminal
yarn lint --scope @packages/server
yarn workspace @packages/server check-ts

转换后的规范至少运行两次。test-unit 需在真实终端运行,因为 util/tty_spec 依赖 process.stdout.getWindowSize,仅 TTY 存在。对比 mocha 原文件与 vitest 文件中 it( 的静态数量,二者必须一致。

代码风格

单引号、无分号、2 空格缩进、尾随逗号、return 前空行、类型导入使用 import type、无 console。尽量不写注释;必要时用一两行说明原因并描述当前代码,而非过去如何。