node:test & node:assert
Node 24 ships a native test runner and assertion module so unit tests run without Jest or Vitest for pure backend logic.
Search across all documentation pages
Node 24 ships a native test runner and assertion module so unit tests run without Jest or Vitest for pure backend logic.
Quick-reference recipe card - copy-paste ready.
import assert from "node:assert/strict";
import { describe, it } from "node:test";
describe("math", () => {
it("adds", () => {
assert.equal(1 + 1, 2);
});
});node --import tsx --testWhen to reach for this:
// src/pricing/discount.ts
export function applyDiscount(cents: number, percent: number): number {
if (percent < 0 || percent > 100) throw new Error("invalid percent");
return Math.round(cents * (1 - percent / 100));
}// test/pricing/discount.test.ts
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { applyDiscount } from "../../src/pricing/discount.js";
describe("applyDiscount", () => {
it("reduces price by percentage", () => {
assert.equal(applyDiscount(1000, 10), 900);
});
it("throws on invalid percent", () => {
assert.throws(() => applyDiscount(1000, -1), /invalid percent/);
});
});{
"scripts": {
"test": "node --import tsx --test --test-reporter spec"
}
}What this demonstrates:
.js extensions for NodeNext compatibility.assert.throws validates error paths without a mocking library.--test-reporter spec gives readable CI output.node:test discovers *.test.ts files (or explicit paths) and runs in parallel by default.before, after, beforeEach, afterEach at describe scope.node:assert/strict throws AssertionError on mismatch (deep equal uses === rules for primitives).it nesting or test.context() for fine-grained grouping.| API | Use |
|---|---|
assert.equal | Primitive equality |
assert.deepEqual | Object/array structure |
assert.rejects | Async throw |
assert.match | Regex on strings |
node --import tsx --testtsx loader compiles TypeScript on the fly for dev and CI.tsc build; tests are not shipped.node:test/mock (Node 22+). Fix: use mock.fn() from node:test or add Vitest for heavy mocking.describe(..., { concurrency: 1 }) or isolate state per test.node --test fails on .ts. Fix: devDependency tsx and --import tsx..ts in tests - Breaks ESM resolution. Fix: import .js paths matching emit layout.| Alternative | Use When | Don't Use When |
|---|---|---|
| Vitest | Mocking, snapshots, Vite ecosystem | Tiny lib avoiding deps |
| Jest | Brownfield suite already on Jest | Greenfield with no legacy |
| tap / ava | TAP output pipelines | Team standardized on node:test |
Yes on Node 18+; Node 24 Active LTS is the target. Stable API for describe/it/assert.
node --import tsx --test test/pricing/discount.test.tsimport { mock } from "node:test";
const fn = mock.fn(() => 42);Available in modern Node versions for simple spy needs.
Yes for unit testing services in isolation. Nest e2e often uses Jest by default; node:test works with Test.createTestingModule manual wiring.
it.skip("reason", fn) or describe.skip for temporary quarantine with visible skip in reporter.
spec human-readable; tap for parsers; dot minimal output.
Return promises from it callbacks or use async functions; failures reject the test.
node --import tsx --experimental-test-coverage --testExperimental; Vitest/Istanbul mature for coverage gates.
No built-in watch; use tsx watch --test patterns or Vitest for TDD loop.
before runs once per describe; beforeEach before each it; opposite for after hooks.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 18, 2026