Node.js 开发手册NODE.JS

CHAPTER 07生产层 · TESTING

测试与质量

内置 test runner、Jest/Vitest、supertest 集成测试、mock 与覆盖率。让服务可信、可改。

开始阅读难度中级先修第五、六章约2小时
07.1

测试金字塔

大量单元测试(快、隔离)、适量集成测试(跨模块真实协作)、少量 E2E(端到端全链路)。比例失衡会导致"改不动"。

优先测行为

测"输入-输出"与关键边界,而非逐行实现。实现改动不应大改测试。

07.2

内置 node:test

Node 20 自带 node:test 与 node:assert,无需额外依赖即可跑单元测试。

import { test } from 'node:test';
import assert from 'node:assert/strict';

test('加法正确', () => {
  assert.equal(1 + 1, 2);
});

test('async 测试', async () => {
  const r = await fetchUser(1);
  assert.ok(r.id === 1);
});
node --test            # 运行 test 目录下的 *.test.js
07.3

Vitest / Jest

Vitest 兼容 Jest API、原生 ESM、速度更快;Jest 生态成熟。提供 describe/it/expect 结构。

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

describe('calc', () => {
  it('返回两数之和', () => {
    expect(add(1, 2)).toBe(3);
  });
});
07.4

断言与匹配

用 matcher 表达期望:toBe/toEqual(值/结构)、toThrow(异常)、resolves/rejects(异步)。

expect(foo).toEqual({ a: 1 });
await expect(p).rejects.toThrow('boom');
expect(() => bad()).toThrow();
07.5

集成测试 supertest

supertest 直接对 Express/Fastify app 发起 HTTP 断言,无需起真实端口,验证路由+中间件+校验全链路。

import request from 'supertest';
import { app } from '../src/app.js';

it('GET /health 返回 200', async () => {
  const res = await request(app).get('/health');
  expect(res.status).toBe(200);
  expect(res.body.ok).toBe(true);
});
07.6

Mock 与替身

用 mock 隔离数据库、第三方 API 等慢/不稳定依赖,让单测快且确定。Vitest 提供 vi.fn/vi.mock。

import { vi } from 'vitest';
const sendMail = vi.fn().mockResolvedValue(true);
await service.notify();
expect(sendMail).toHaveBeenCalledOnce();
只 mock 边界

优先 mock 进程外的依赖(网络、DB、时钟),内部纯函数直接测,避免过度 mock 导致测试失真。

07.7

覆盖率

覆盖率衡量代码被测试触及的比例,但不能替代质量。目标先达关键路径,再逐步提升。

vitest run --coverage
npx c8 node --test          # 内置 runner 配 c8
覆盖率陷阱

100% 覆盖不等于无 bug(可能只走通没断言行为)。关注"分支覆盖"与"关键断言"。

07.8

常见陷阱与 FAQ

测试间状态互相污染怎么办?

用 beforeEach/afterEach 重置状态与 mock,保证用例独立可单独运行。

异步测试没 await 会怎样?

测试在断言前就结束,失败也可能被吞掉。确保返回 Promise 或显式 await。

集成测试连真实库吗?

可用 Testcontainers 起真实库,或用内存库(如 sqlite :memory:)保证速度与真实性的平衡。

07.9

实战练习与资源

延伸资源