Node.js 内置测试运行器入门指南

Node.js 18 起内置测试运行器,无需安装任何第三方依赖即可写测试。本文讲解 node:test 的编写与运行、describe/it 语法、用例过滤、Mock、钩子、覆盖率与测试报告。

最佳实践
测试检测与质量验证插画

本文依据官方文档整理,未执行运行验证或性能基准。代码片段展示局部用法,业务函数、数据和环境需按项目补齐;版本与配置以所引文档为准。

直接回答:Node.js 从 v18 开始内置了官方测试运行器(node:test),无需安装 Jest、Mocha 等外部依赖就能编写、运行测试,并自带 Mock、钩子、覆盖率与报告能力——Node 项目的"零依赖测试"时代正式到来。

背景

长期以来 Node.js 没有官方测试方案,社区形成了 Jest、Mocha、Tap 等第三方生态。后来核心团队将测试运行器并入 Node 本体,v18 引入,v20 的核心运行器稳定;mock、快照、覆盖率等子功能各自有版本和稳定性边界。对于想减少依赖的项目,这是当下最值得关注的默认选项。

第一个测试

本文按支持这些API的现代Node版本演示;先在 package.json 设置 "type": "module"(或改用 .mjs)。新建 math.js:

export function add(a, b) {
  return a + b;
}

再建 math.test.js:

import { test } from "node:test";
import assert from "node:assert/strict";
import { add } from "./math.js";

test("add 函数", async (t) => {
  await t.test("1 + 2 等于 3", () => {
    assert.equal(add(1, 2), 3);
  });

  await t.test("负数相加", () => {
    assert.equal(add(-1, -1), -2);
  });
});

运行:

node --test

Node 会自动发现 *.test.js 等约定命名的文件并执行。

describe/it 语法(可选)

习惯 BDD 风格的话,内置 runner 也提供:

import { describe, it } from "node:test";

describe("add", () => {
  it("1 + 2 = 3", () => {
    assert.equal(add(1, 2), 3);
  });
});

node:assert/strict 是推荐断言库——assert.equal 在 strict 模式下就是严格相等,避免传统 assert 的隐式转换坑。

过滤与限定

  • node --test math.test.js:指定文件;
  • node --test --test-name-pattern="负数":按名称过滤;
  • 代码里 t.test("...", { only: true }, ...) 配合 node --test --test-only:只跑标记用例;
  • node --test --watch:监视模式。

Mock 能力

内置 runner 自带 mock 工具:

import { mock } from "node:test";

const mockedFetch = mock.fn(async () => ({ ok: true }));
const request = async (fetcher) => fetcher('https://example.test');
await request(mockedFetch);
assert.equal(mockedFetch.mock.callCount(), 1);

mock.method(obj, "methodName") 可以替换对象方法,mock.timers 还能接管定时器做时间相关测试。

钩子

test("套件", async (t) => {
  t.before(() => { /* 套件前 */ });
  t.beforeEach(() => { /* 每条前 */ });
  t.afterEach(() => { /* 每条后 */ });
  t.after(() => { /* 套件后 */ });
});

覆盖率与报告

node --test --experimental-test-coverage

直接输出行/分支/函数覆盖率。报告方面,当前默认报告器取决于是否连接TTY及版本,不应假定总是TAP;可显式指定 --test-reporter=tap,也可以通过 --test-reporter=spec 获得更易读的格式,或输出 JUnit XML 供 CI 展示。

一个简单的 HTTP 服务测试

import { test } from "node:test";
import assert from "node:assert/strict";
import { createServer } from "node:http";
import { once } from "node:events";

test("服务器返回 200", async (t) => {
  const server = createServer((req, res) => {
    res.writeHead(200).end("ok");
  });
  t.after(() => new Promise((resolve, reject) => {
    server.close(err => err ? reject(err) : resolve());
    server.closeAllConnections();
  }));
  server.listen(0, "127.0.0.1");
  await once(server, "listening");

  const port = server.address().port;
  const res = await fetch(`http://localhost:${port}/`);
  assert.equal(res.status, 200);
  assert.equal(await res.text(), "ok");
});

端口传 0 让系统分配,避免并行测试撞端口。

常见问题(FAQ)

Q:内置 runner 能完全替代 Jest 吗?
A:对大多数 Node 后端项目:可以。前端组件快照、jsdom 环境等仍是 Jest/Vitest 的主场。后端 API、库、CLI 工具用内置 runner 已经非常顺手。

Q:TypeScript 项目怎么用?
A:Node 的内置类型剥离支持与默认开关随版本变化,且不做类型检查、不读取 tsconfig 路径转换;也可使用兼容的 tsx 加载方案。生产项目建议还是先编译再测,保持与线上产物一致。

Q:并行执行怎么控制?
A:默认测试文件间并行、文件内顺序执行。--test-concurrency 调整并行度;同一文件内的子测试默认串行,需要并发时显式设置 concurrency: true。

官方参考

资料核对日期:2026-09-29。

延伸阅读

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台