Built-in testing means zero-dependency, fast tests that work in every Node project.
Node ships a complete test runner in `node:test` — no Jest or Mocha required for most projects.
- Organise with
describe(name, fn)andit(name, fn)(ortest). Async functions just work: the test waits for the promise. - Hooks:
before,after,beforeEach,afterEach. - Modifiers:
it.skip,it.todo,it.only(run with--test-only).
Assertions come from `node:assert/strict`: equal (strict ===), deepEqual (structural), ok, match (regex), throws for sync code and `await assert.rejects(promise, matcher)` for async code. Always use the strict variant; the legacy one uses loose ==.
Run tests with node --test. With no arguments it finds files matching patterns such as **/*.test.js, **/*-test.js and files in test/ directories, and runs each file in its own process in parallel. Useful flags:
--watchreruns on change.--test-name-pattern="login"filters tests.--experimental-test-coverageprints line/branch coverage.--test-reporter=spec|dot|tap|junitchanges output (junit for CI).
Mocking is built in through the t.mock / mock object: mock.fn() creates a spy recording calls, mock.method(obj, "name", impl) replaces a method and restores it after the test, and mock.timers.enable() fakes setTimeout and Date so time-dependent code runs instantly.
Good tests target behaviour: call the public function or the HTTP endpoint, assert on outputs and side effects. Keep each test independent — create fresh state in beforeEach rather than relying on test order.
Example
// test/cart.test.js — run: node --test
import { describe, it, beforeEach, mock } from "node:test";
import assert from "node:assert/strict";
import { Cart } from "../src/cart.js"; // add(sku, qty) and async total() using prices.get
describe("Cart", () => {
let prices;
beforeEach(() => { prices = { get: mock.fn(async (sku) => ({ apple: 2, pear: 3 })[sku]) }; });
it("sums item prices", async () => {
const cart = new Cart(prices);
cart.add("apple", 2); cart.add("pear");
assert.equal(await cart.total(), 7);
assert.equal(prices.get.mock.callCount(), 2);
});
it("rejects non-positive quantities", () => {
assert.throws(() => new Cart(prices).add("apple", 0), RangeError);
});
});Remember
node --testruns*.test.jsfiles, each in its own process- Use
node:assert/strict;assert.rejectsfor async errors mock.fn,mock.methodandmock.timerscover most mocking
Watch out for
- Writing
assert.rejects(promise)withoutawait, so the test passes before the check runs.