Testing with node:test

Node.js · Practical · 14 min

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) and it(name, fn) (or test). 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:

  • --watch reruns on change.
  • --test-name-pattern="login" filters tests.
  • --experimental-test-coverage prints line/branch coverage.
  • --test-reporter=spec|dot|tap|junit changes 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 --test runs *.test.js files, each in its own process
  • Use node:assert/strict; assert.rejects for async errors
  • mock.fn, mock.method and mock.timers cover most mocking

Watch out for

  • Writing assert.rejects(promise) without await, so the test passes before the check runs.

Check yourself

  1. Which assertion correctly checks that an async function rejects?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: nodejs.org/docs/latest-v22.x/api/test.html