Node.js 测试框架对比

常用 JavaScript/TypeScript 测试框架的横向对比与选型建议。快速参考——实践示例见各课程。

一、框架总览

框架代际发布时间理念一句话
Mocha + Chai + Sinon第一代2012拼接式:自己组装 runner、断言库、mock 库极其灵活,但配置成本高,新项目不推荐
Jest第二代2015一体化:零配置、内置断言/mock/coverage/快照生态最大,但 ESM 支持弱、TypeScript 配置痛
node:test第二代2022 (Node 18)内置:Node.js 原生,零依赖适合纯函数 + fixture 场景,mock 能力有限
Vitest第三代2022Vite 驱动:原生 ESM、极速 HMR、Jest 兼容 API2026 年新项目最推荐

二、核心差异矩阵

维度MochaJestnode:testVitest
零配置开箱即用❌ 需装 3-4 个包✅(但有坑)✅ 真正零依赖
TypeScript 支持需 ts-node/tsx需 ts-jest 或 @swc/jest需 tsx 或先 tsc✅ 原生理解 TS
ESM 支持⚠️ 需配置⚠️ experimental✅(Node 原生)✅ ESM-first
内置断言❌ 需 Chaiexpect()assertexpect()
内置 mock/spy❌ 需 Sinonjest.fn()⚠️ Node 22+ 有基础 mockvi.fn()
快照测试toMatchSnapshot()toMatchSnapshot()
内置 coverage❌ 需 nyc/c8--coverage⚠️ --experimental-test-coverage--coverage
Watch 模式速度中等快(无 transform)极快(Vite HMR)
并行执行❌ 默认串行✅ 文件级并行✅ 文件级并行✅ 文件级并行 + 线程池
学习曲线中(需学 3 个库)极低低(Jest 兼容 API)

三、什么时候选哪个

场景推荐理由
零依赖原型 / 小型 CLI 工具node:test不需要安装任何东西,Node 18+ 直接可用
纯函数测试 + fixture 文件node:test不需要 mock,assert 完全够用
需要 mock 外部依赖Vitestvi.mock() 比手写 stub/sinon 简洁得多
VS Code 扩展(需 mock vscode API)Vitestmock StatusBarItem、window、workspace 等 VS Code API
Vite 项目(前端)Vitest复用 vite.config.ts,HMR 级速度
React NativeJestRN 工具链深度绑定 Jest
已有 5000+ Jest 测试的存量项目Jest迁移成本高于收益,除非团队有明确计划
需要非标准 runner 行为Mocha程序化 API 最灵活(95% 项目不需要)
2026 年启动的新 JS/TS 项目Vitest当前最优默认选择

四、API 签名速查

node:test + node:assert/strict

import { describe, it, before, after, beforeEach, afterEach } from 'node:test';
import assert from 'node:assert/strict';

describe('suite', () => {
  it('case', () => {
    assert.equal(actual, expected);
    assert.deepEqual(obj, other);
    assert.match(str, /regex/);
    assert.doesNotMatch(str, /bad/);
    assert.ok(truthy);
    assert.throws(() => fn());
  });
});

// 运行:node --test out/test.js
// TypeScript:node --import tsx --test src/test.ts

Vitest (Jest-compatible API)

import { describe, it, expect, vi, beforeEach } from 'vitest';

describe('suite', () => {
  it('case', () => {
    expect(actual).toBe(expected);
    expect(obj).toEqual(other);
    expect(str).toMatch(/regex/);
    expect(truthy).toBeTruthy();
    expect(() => fn()).toThrow();
  });
});

// Mock 示例
const mockFn = vi.fn().mockReturnValue(42);
vi.mock('fs', () => ({ readFileSync: vi.fn() }));

// 运行:npx vitest

Jest

// API 与 Vitest 几乎相同——替换 import 来源即可:
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
// 或者不 import,依赖 Jest 的全局注入

const mockFn = jest.fn().mockReturnValue(42);
jest.mock('fs', () => ({ readFileSync: jest.fn() }));

// 运行:npx jest

五、速度基准参考

大致量级对比(200 个测试的套件,纯函数,无 I/O)——精确数字取决于具体项目,但相对关系稳定。

框架冷启动Watch 增量说明
Vitest~1.2s~50msVite HMR——只重跑受影响的测试文件
node:test~1.5s~1.5s无 transform 开销,但 watch 模式重跑整个文件
Jest~8s~2sts-jest 做完整类型检查是主要瓶颈
Jest + @swc/jest~3s~1s用 SWC 替代 tsc 做 transform,快不少
Mocha + tsx~2s无内置 watch,需配合 --watch 或 nodemon

六、VS Code 扩展测试的特殊考量

VS Code 扩展测试有两个独特挑战:

6.1 Mock VS Code API

// 扩展代码中大量使用:
import * as vscode from 'vscode';
vscode.window.createStatusBarItem(...)
vscode.workspace.getConfiguration(...)

// 在测试中直接 import 'vscode' 会失败——
// vscode 模块只在 VS Code 扩展宿主进程中存在。
// 解决方案:mock 掉整个 vscode 模块。

Vitest 的 vi.mock() 最适合这个场景:

// __mocks__/vscode.ts
export const window = {
  createStatusBarItem: vi.fn(() => ({ show: vi.fn(), text: '' })),
  showInformationMessage: vi.fn(),
};
export const workspace = {
  getConfiguration: vi.fn(() => ({ get: vi.fn() })),
};

如果只用 node:test,你需要手写 stub 或引入 sinon——都比 vi.mock() 更啰嗦。

6.2 哪些测试不需要 mock

纯数据变换函数(getLatestTokenCountdecodeProjectPathformatTokens)不碰 VS Code API——用 node:test + fixture 文件测试完全足够。这就是 Lesson 0007 选 node:test 的原因:在不需要 mock 的时候,零依赖是最优解

七、迁移路径

难度说明
node:testVitestAPI 不同但逻辑相同——改写断言语法即可。当 mock 需求出现时迁移
JestVitestAPI 几乎一样——改 import 来源、删掉 jest.config.ts、装 vitest 即可
MochaVitest需要从 Chai 断言迁移到 expect(),从 Sinon mock 迁移到 vi.fn()