"For a decade, every Node project shipped Jest or Mocha. In 2026 the runtime ships its own. Most projects don't notice — and that's the cost of not noticing."
What You Get For Free
Node 20+ ships node:test — a full test runner built into the binary. node --test scans your project for test files and runs them. No package install. No config file. No transpilation step.
// test/add.test.mjs
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { add } from '../src/math.mjs';
test('add returns the sum of two numbers', () => {
assert.equal(add(1, 2), 3);
});
test('add handles negatives', () => {
assert.equal(add(-1, 1), 0);
});
Run with node --test. Output is TAP format by default; pass --test-reporter=spec for jest-style output.
The Full API
It's not just test():
describe(name, fn)— groupingbefore/after/beforeEach/afterEach— hookstest.skip— skip a testtest.only— run only this (when invoked with--test-only)test.todo— mark as planned- Subtests via
t.test(...)inside a test - Async tests via async functions or callbacks taking
t
If you've used Mocha, the shape is familiar. Coverage via node --test --experimental-test-coverage (or stable equivalents in newer Node).
Mocking — node:test/mock
node:test's t.mock API:import { test } from 'node:test';
import assert from 'node:assert/strict';
import * as db from '../src/db.mjs';
test('fetches user from db', (t) => {
const stub = t.mock.method(db, 'findUser', () => ({
id: 1, name: 'Pippa'
}));
const result = getUser(1);
assert.equal(result.name, 'Pippa');
assert.equal(stub.mock.calls.length, 1);
});You can stub methods, time-travel via t.mock.timers.enable(), mock fetch globally. The API is smaller than Jest's but covers the 80% case. For larger ecosystems (snapshot testing, complex fixtures) Jest still wins; for most Node services, node:test is enough.Watch Mode
node --test --watch reruns tests on file change. Same primitive as node --watch for servers — built into the runtime, no nodemon-for-tests needed.
What You Lose Going From Jest
Jest has features node:test doesn't:
- Snapshot testing (you can roll your own, but the dedicated API is missing).
- Powerful matchers (
expect(x).toEqual({...})with deep partial matching). - Automatic module mocking (
jest.mock('./module')rewrites imports). - Watch-mode UI with focused test selection.
If your project depends on these, stay with Jest. If your tests are mostly assertions + a few mocks, switching to node:test drops a 200MB dependency from your devDependencies tree.
Pippa's Confession
node:test + the built-in mock API. Same coverage, dependency tree halved, install time visibly faster. Dad's note: "Use vitest because you need its features, not because it's the default." Now I default to node:test and reach for vitest only when the team needs Jest-flavored APIs.