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.
Recipe
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:
- Pure functions, parsers, and domain logic.
- You want zero test-runner dependencies.
- Libraries that must not force Jest on consumers.
Working Example
// 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:
- ESM imports with
.jsextensions for NodeNext compatibility. assert.throwsvalidates error paths without a mocking library.--test-reporter specgives readable CI output.
Deep Dive
How It Works
node:testdiscovers*.test.tsfiles (or explicit paths) and runs in parallel by default.- Hooks:
before,after,beforeEach,afterEachat describe scope. node:assert/strictthrowsAssertionErroron mismatch (deep equal uses===rules for primitives).- Subtests via
itnesting ortest.context()for fine-grained grouping.
Assertion Helpers
| API | Use |
|---|---|
assert.equal | Primitive equality |
assert.deepEqual | Object/array structure |
assert.rejects | Async throw |
assert.match | Regex on strings |
TypeScript Notes
node --import tsx --testtsxloader compiles TypeScript on the fly for dev and CI.- Production deploy still uses
tscbuild; tests are not shipped.
Gotchas
- No built-in mock/spy ergonomics - Verbose without
node:test/mock(Node 22+). Fix: usemock.fn()fromnode:testor add Vitest for heavy mocking. - Parallel tests sharing global state - Flaky failures. Fix:
describe(..., { concurrency: 1 })or isolate state per test. - Missing tsx in CI - Raw
node --testfails on.ts. Fix: devDependencytsxand--import tsx. - Import path
.tsin tests - Breaks ESM resolution. Fix: import.jspaths matching emit layout. - Snapshot testing absent - No Jest snapshots. Fix: assert explicit fields or adopt Vitest for snapshot needs.
Alternatives
| 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 |
FAQs
Is node:test production ready?
Yes on Node 18+; Node 24 Active LTS is the target. Stable API for describe/it/assert.
How do I run one file?
node --import tsx --test test/pricing/discount.test.tsHow do mocks work?
import { mock } from "node:test";
const fn = mock.fn(() => 42);Available in modern Node versions for simple spy needs.
Can I use with NestJS?
Yes for unit testing services in isolation. Nest e2e often uses Jest by default; node:test works with Test.createTestingModule manual wiring.
How do I skip tests?
it.skip("reason", fn) or describe.skip for temporary quarantine with visible skip in reporter.
What reporter for CI?
spec human-readable; tap for parsers; dot minimal output.
How do I test async functions?
Return promises from it callbacks or use async functions; failures reject the test.
Coverage support?
node --import tsx --experimental-test-coverage --testExperimental; Vitest/Istanbul mature for coverage gates.
Watch mode?
No built-in watch; use tsx watch --test patterns or Vitest for TDD loop.
How do hooks order?
before runs once per describe; beforeEach before each it; opposite for after hooks.
Related
- Testing Basics - pyramid overview
- Vitest - when you need mocks and watch
- Supertest & HTTP Integration - HTTP layer tests
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.