JavaScript · Course navigation

JavaScript: unit tests with node:assert

Write small tests with assertions, including checks that bad input throws the right error.

On this page
Before you start

JavaScript functions and exceptions.

  • Write small tests with assertions, including checks that bad input throws the right error.
  • Predict the sample output and explain the state changes.
  • Complete the practice task and check the boundary cases.

The concept

A unit test calls a function with known input and asserts the expected result. Node.js includes the assert module, and node:test is its built-in test runner. The strict mode of assert compares with ===, which avoids the surprises of loose equality.

The test helper below runs each test in try/catch, prints PASS or FAIL, and counts failures. assert.throws checks both that an error is thrown and what kind. Testing the invalid cases matters as much as the valid ones. Note that Number('') is 0, so checking only that Number returns something is not enough for validation.

Code example

JavaScript
const assert = require("node:assert/strict");

function percent(text) {
  const n = Number(text);
  if (text.trim() === "" || !Number.isInteger(n) || n < 0 || n > 100) {
    throw new RangeError("invalid percent: " + text);
  }
  return n;
}

let failures = 0;
function test(name, fn) {
  try {
    fn();
    console.log("PASS " + name);
  } catch (error) {
    failures++;
    console.log("FAIL " + name + ": " + error.message);
  }
}

test("parses 42", () => assert.equal(percent("42"), 42));
test("accepts the boundary 100", () => assert.equal(percent("100"), 100));
test("rejects 101", () => assert.throws(() => percent("101"), RangeError));
test("rejects an empty string", () => assert.throws(() => percent("")));
test("reports the bad text", () => assert.throws(() => percent("abc"), { message: "invalid percent: abc" }));
console.log("failures: " + failures);
Output
PASS parses 42
PASS accepts the boundary 100
PASS rejects 101
PASS rejects an empty string
PASS reports the bad text
failures: 0

Follow the code

Open the walkthrough to inspect the example one step at a time.

Show guided walkthrough
Guided walkthroughReady
1const assert = require("node:assert/strict");
2
3function percent(text) {
4 const n = Number(text);
5 if (text.trim() === "" || !Number.isInteger(n) || n < 0 || n > 100) {
6 throw new RangeError("invalid percent: " + text);
7 }
8 return n;
9}
10
11let failures = 0;
12function test(name, fn) {
13 try {
14 fn();
15 console.log("PASS " + name);
16 } catch (error) {
17 failures++;
18 console.log("FAIL " + name + ": " + error.message);
19 }
20}
21
22test("parses 42", () => assert.equal(percent("42"), 42));
23test("accepts the boundary 100", () => assert.equal(percent("100"), 100));
24test("rejects 101", () => assert.throws(() => percent("101"), RangeError));
25test("rejects an empty string", () => assert.throws(() => percent("")));
26test("reports the bad text", () => assert.throws(() => percent("abc"), { message: "invalid percent: abc" }));
27console.log("failures: " + failures);

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
Reject empty text, non-integers, and values outside 0 to 100.—
—
A tiny runner: run the test body and report the outcome.—
—
A normal case.—
PASS parses 42
The error type is checked too.—
PASS parses 42
PASS accepts the boundary 100
PASS rejects 101
All five checks pass.failures = 0
PASS parses 42
PASS accepts the boundary 100
PASS rejects 101
PASS rejects an empty string
PASS reports the bad text
failures: 0

Behind the scenes

How JavaScript processes this code

A JavaScript engine parses and runs the source. A function body runs when the function is called. console.log writes to the console.

Cost and common mistakes

Each test is O(1) plus the code under test.

  • Number('') and Number(' ') are 0, and Number('1e2') is 100. Validate the text itself, not just the converted number.
  • A test that throws for the wrong reason can still pass assert.throws if you do not check the error type or message.
  • For larger projects, use the built-in node:test runner, which adds test discovery, hooks, and reports.

Try it yourself

Add a test for a negative number.

Reveal answer and reasoning

test("rejects -1", () => assert.throws(() => percent("-1"), RangeError)); -1 is below the allowed minimum of 0.

Further reading