选 Vitest 还是 Jest
| Vitest | Jest | |
|---|---|---|
| 速度 | 快(基于 Vite) | 中 |
| ESM 支持 | 原生 | 需要配置 |
| TypeScript | 一等公民 | 需配 babel/ts-jest |
| API | 几乎兼容 Jest | 老牌 |
| 推荐 | 新项目 | 老项目维护 |
下面用 Vitest 举例。Jest 用户大多数 API 直接通用。
装
npm install -D vitest
package.json:
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"coverage": "vitest run --coverage"
}
}
第一个测试
// math.js
export function add(a, b) {
return a + b;
}
// math.test.js
import { describe, it, expect } from 'vitest';
import { add } from './math.js';
describe('add', () => {
it('adds two numbers', () => {
expect(add(1, 2)).toBe(3);
});
it('handles negatives', () => {
expect(add(-1, 1)).toBe(0);
});
});
npm test
常用 expect 匹配
expect(42).toBe(42); // === 严格相等
expect({a: 1}).toEqual({a: 1}); // 深比较
expect([1,2,3]).toContain(2);
expect('hello').toMatch(/ello/);
expect(arr).toHaveLength(3);
expect(obj).toHaveProperty('name');
expect(fn).toThrow();
expect(fn).toThrow('specific message');
expect(promise).resolves.toBe('ok');
expect(promise).rejects.toThrow();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeTruthy();
expect(value).toBeGreaterThan(10);
异步测试
it('fetches user', async () => {
const user = await getUser(42);
expect(user.name).toBe('Alice');
});
前后钩子
import { beforeAll, beforeEach, afterAll, afterEach } from 'vitest';
describe('users', () => {
beforeAll(async () => {
// 一次:测试开始前
await db.connect();
});
afterAll(async () => {
await db.close();
});
beforeEach(async () => {
// 每个测试前
await db.users.deleteMany({});
});
afterEach(() => {});
it(...);
});
Mock
简单函数 mock
import { vi } from 'vitest';
const fn = vi.fn(() => 42);
fn();
expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenCalledWith(/* args */);
expect(fn).toHaveBeenCalledTimes(1);
expect(fn).toHaveReturnedWith(42);
Mock 模块
import { vi } from 'vitest';
import { sendEmail } from './email.js';
vi.mock('./email.js', () => ({
sendEmail: vi.fn().mockResolvedValue({ ok: true }),
}));
it('sends email on signup', async () => {
await signup({ email: 'a@x' });
expect(sendEmail).toHaveBeenCalledWith('a@x', expect.stringContaining('welcome'));
});
Mock 时间
vi.useFakeTimers();
const fn = vi.fn();
setTimeout(fn, 1000);
vi.advanceTimersByTime(1000);
expect(fn).toHaveBeenCalled();
vi.useRealTimers();
集成测试 HTTP API(supertest)
npm install -D supertest
import request from 'supertest';
import { app } from './app.js';
describe('GET /users', () => {
it('returns list', async () => {
const res = await request(app)
.get('/users')
.expect(200);
expect(res.body).toBeInstanceOf(Array);
});
it('POST creates user', async () => {
const res = await request(app)
.post('/users')
.send({ name: 'Alice', email: 'a@x' })
.expect(201);
expect(res.body.id).toBeDefined();
});
});
supertest 自动启动 / 关闭 app——不用 listen 真端口。
测试数据库
两种姿势:
方式 A · 内存 DB(快)
SQLite in-memory / Postgres in Docker / mongodb-memory-server。
// 全局 setup
beforeAll(async () => {
await db.connect(':memory:');
await db.migrate();
});
方式 B · 真 DB + 事务回滚
beforeEach(async () => {
await db.beginTransaction();
});
afterEach(async () => {
await db.rollback(); // 每个测试自动清理
});
覆盖率
npm install -D @vitest/coverage-v8
# vitest.config.js 加 coverage 配置
npm run coverage
输出 coverage/index.html 看具体哪些行没覆盖。
测试金字塔
E2E 测试 (Playwright / Cypress) ← 少
/
集成测试(supertest) ← 中
/
单元测试(Vitest) ← 多
单元测试占大头——快 + 易写 + 易维护。
坑
- 测试不要依赖外部网络 / 真数据库——慢 + 不可靠
- Mock 太多 → 测试和实现耦合 → 重构难
- 异步测试忘 await = 测试通过但断言没生效
- 全局状态测试间共享 → 互相影响(用 beforeEach 清理)
下一篇:TypeScript 接入。