Appearance
JavaScript Testing
Test module exports, function signatures, and observable behaviors — not implementation details. If tests run in Node.js, use node:assert/strict for assertions.
The pattern
Test module exports explicitly
Verify modules export what they claim with correct types:
javascript
import { test } from "node:test";
import assert from "node:assert/strict";
import {
processData,
validateInput,
DEFAULT_CONFIG,
} from "./data-processor.js";
test("exports required functions", () => {
assert.strictEqual(typeof processData, "function");
assert.strictEqual(typeof validateInput, "function");
assert.strictEqual(typeof DEFAULT_CONFIG, "object");
});Verify:
- Every public export has an explicit existence test
- Export types match expected interface contracts
Test function signatures before behavior
Validate function names and return types before testing behavior:
javascript
import { describe, test } from "node:test";
import assert from "node:assert/strict";
import { calculateTotal } from "./math-utils.js";
describe("calculateTotal function", () => {
test("has correct name", () => {
assert.strictEqual(
calculateTotal.name,
"calculateTotal",
);
});
test("returns number", () => {
const result = calculateTotal([1, 2]);
assert.strictEqual(typeof result, "number");
});
test("calculates sum correctly", () => {
assert.strictEqual(calculateTotal([1, 2, 3]), 6);
});
});Verify:
- Function name matches expected identifier
- Return type matches expected interface
One test per observable behavior
Focus tests on externally visible outcomes rather than internal implementation.
DON'T test implementation details:
javascript
test("uses quicksort algorithm", () => {
const spy = sinon.spy(sortUsers, "_quicksort");
sortUsers([{ name: "Bob" }, { name: "Alice" }]);
assert.ok(spy.called);
});DO test observable behavior:
javascript
test("sorts users by name alphabetically", () => {
const users = [
{ name: "Bob" },
{ name: "Alice" },
];
const expected = [
{ name: "Alice" },
{ name: "Bob" },
];
const actual = sortUsers(users);
assert.deepStrictEqual(actual, expected);
});Define expected and actual before comparing
Extract values into named constants for clarity and easier debugging:
DON'T inline values in assertions:
javascript
test("calculates discount", () => {
assert.strictEqual(applyDiscount(100, 0.2), 80);
});DO define expected and actual constants:
javascript
test("calculates discount", () => {
const expected = 80;
const actual = applyDiscount(100, 0.2);
assert.strictEqual(actual, expected);
});Named constants make test failures easier to diagnose and encourage thinking about the expected outcome before running code.
Exception: Don't use this pattern for boolean checks. Use assert.ok when testing truthiness:
javascript
// Good — assert.ok for boolean conditions
assert.ok(fs.existsSync(outputPath));
assert.ok(results.length > 0);
// Unnecessary — don't wrap booleans in actual/expected
const expected = true;
const actual = fs.existsSync(outputPath);
assert.strictEqual(actual, expected);Choose assertion methods by value type
Use strictEqual for primitives and deepStrictEqual for objects/arrays:
javascript
// Primitives — use strictEqual
const expected = 42;
const actual = calculateTotal(items);
assert.strictEqual(actual, expected);
// Objects/Arrays — use deepStrictEqual
const expected = { name: "Alice", role: "admin" };
const actual = getUser("alice");
assert.deepStrictEqual(actual, expected);Never reach inside actual and expected
Once you have expected and actual, compare them directly. Never access their properties in assertions:
DON'T reach inside actual and expected:
javascript
test("returns config object", () => {
const expected = {
supervisor: cfg1,
server: cfg2,
db: cfg3,
worktree: "/tmp",
};
const actual = getConfig();
// Wrong — reaching inside actual and expected
assert.deepStrictEqual(
actual.supervisor,
expected.supervisor,
);
assert.deepStrictEqual(
actual.server,
expected.server,
);
assert.deepStrictEqual(actual.db, expected.db);
assert.strictEqual(
actual.worktree,
expected.worktree,
);
});DO compare actual and expected directly:
javascript
test("returns config object", () => {
const expected = {
supervisor: cfg1,
server: cfg2,
db: cfg3,
worktree: "/tmp",
};
const actual = getConfig();
assert.deepStrictEqual(actual, expected);
});If you're reaching into properties, either assign the right values to actual and expected in the first place, or use deepStrictEqual to compare the full objects.