Testing JavaScript in Node

Introduction

Automated tests catch regressions before users do. Node ships node:test and node:assert; many projects add Vitest or Jest for richer DX. This chapter writes small unit tests for pure functions and async code—patterns that apply in front-end and back-end repos.

Prerequisites

Pure Function Under Test

math.mjs:

javascript
export function clamp(value, min, max) {
  if (value < min) return min;
  if (value > max) return max;
  return value;
}

node:test + node:assert

math.test.mjs:

javascript
import test from "node:test";
import assert from "node:assert/strict";
import { clamp } from "./math.mjs";
 
test("clamp lower bound", () => {
  assert.equal(clamp(-5, 0, 100), 0);
});
 
test("clamp upper bound", () => {
  assert.equal(clamp(150, 0, 100), 100);
});
 
test("clamp middle", () => {
  assert.equal(clamp(42, 0, 100), 42);
});

Run:

bash
node --test math.test.mjs

Async Test

javascript
import test from "node:test";
import assert from "node:assert/strict";
 
async function fetchStatus() {
  const res = await fetch("https://example.com");
  return res.status;
}
 
test("example.com returns 200", async () => {
  const status = await fetchStatus();
  assert.equal(status, 200);
});

Network tests can be flaky—mock fetch in CI or mark as integration tests.

Vitest (Common in Vite Projects)

bash
npm install --save-dev vitest

package.json:

json
{
  "scripts": {
    "test": "vitest run"
  }
}

math.test.mjs with Vitest-compatible syntax:

javascript
import { describe, it, expect } from "vitest";
import { clamp } from "./math.mjs";
 
describe("clamp", () => {
  it("limits max", () => {
    expect(clamp(200, 0, 100)).toBe(100);
  });
});
bash
npm test

What to Test First

  • Pure utilities (formatting, validation)
  • API handlers with mocked dependencies
  • Edge cases (null, empty array, bad input)

UI tests often use Playwright or Cypress—separate from unit tests.

Arrange–Act–Assert

javascript
// pattern comment structure
test("discount applies", () => {
  // arrange
  const price = 100;
  const rate = 0.1;
  // act
  const total = price * (1 - rate);
  // assert
  assert.equal(total, 90);
});

Mini Example: Test Error Thrown

javascript
import assert from "node:assert/strict";
import test from "node:test";
 
function parsePort(text) {
  const n = Number(text);
  if (Number.isNaN(n)) throw new Error("invalid port");
  return n;
}
 
test("invalid port throws", () => {
  assert.throws(() => parsePort("abc"), /invalid port/);
});

FAQ

How much coverage?

Aim for critical paths and bugs you have seen—not 100% line coverage as a goal itself.

TDD?

Optional—tests help most when requirements are clear.

CI?

Run npm test on every pull request in GitHub Actions or similar.

What comes next?

HTTP clients in Node.