Testing with Node.js Native Test Runner
Starting with Node.js v20, the runtime includes a native test runner and assertion library. This means you can write and run tests without installing third-party frameworks like Jest or Mocha.
1. Writing a Basic Unit Test
The native test runner is available in the node:test module, and assertions can be imported from node:assert:
// math.js
export const add = (a, b) => a + b;
// math.test.js
import test from "node:test";
import assert from "node:assert/strict"; // Strict assertion mode is recommended
import { add } from "./math.js";
test("should add two positive integers correctly", () => {
const result = add(2, 3);
assert.equal(result, 5);
});2. Running the Tests
To find and run all test files in your project, use the --test execution flag:
# Execute all files matching *.test.js or test/*.js
node --testYou can watch files for changes and automatically rerun tests using the watch option:
# Rerun tests on file changes
node --test --watch3. Nesting Tests with describe and it
For larger projects, group related tests using describe block wrappers and it assertions:
import { describe, it } from "node:test";
import assert from "node:assert/strict";
describe("User Model validations", () => {
it("should accept valid username values", () => {
const user = { username: "nodeDev" };
assert.ok(user.username.length >= 3);
});
it("should reject usernames that are too short", () => {
const user = { username: "jo" };
assert.throws(() => {
if (user.username.length < 3) throw new Error("Invalid name");
});
});
});4. Testing Asynchronous Functions
To test asynchronous operations, pass an async callback function to the test runner. The test runner will wait for all promises to resolve:
import test from "node:test";
import assert from "node:assert/strict";
const fetchUserData = async (id) => {
return new Promise((resolve) => {
setTimeout(() => resolve({ id, role: "admin" }), 100);
});
};
test("should fetch asynchronous profile settings", async () => {
const data = await fetchUserData(42);
assert.deepEqual(data, { id: 42, role: "admin" });
});5. Mocking Functions and Timers
The test runner includes built-in mocking helpers. You can track function calls, monitor argument parameters, and override response behaviors:
import test from "node:test";
import assert from "node:assert/strict";
test("should track method invocations using mock helper", (t) => {
const calculator = {
multiply: (a, b) => a * b
};
// Mock the multiply method
const spy = t.mock.fn(calculator.multiply);
const result = spy(4, 5);
assert.equal(result, 20);
assert.equal(spy.mock.calls.length, 1);
assert.deepEqual(spy.mock.calls[0].arguments, [4, 5]);
});