NodeJSTesting in Node.js

Testing in Node.js

Tests are executable specifications: code that runs your code and asserts it behaves as intended. They catch regressions before users do, document expected behaviour, and let you refactor with confidence. The Node.js testing landscape has matured dramatically — there's now a built-in test runner (node:test), alongside established frameworks like Jest, Mocha, and HTTP-testing tools like Supertest. This section walks through the test pyramid, each runner, mocking, coverage, and TDD. This page sets up the why and the vocabulary.

Why test
  • Catch regressions — a change that breaks old behaviour fails a test immediately, not in production.

  • Enable refactoring — you can restructure code freely when tests verify behaviour is preserved.

  • Document behaviour — a well-named test describes what the code is supposed to do.

  • Design feedback — hard-to-test code is often badly designed; testing pressures better structure.

  • Confidence to ship — a green test suite is a precondition for safe deployment.

The anatomy of a test

JS
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { add } from './math.js'

test('add() sums two numbers', () => {
  // Arrange — set up inputs:
  const a = 2, b = 3

  // Act — call the code under test:
  const result = add(a, b)

  // Assert — verify the outcome:
  assert.equal(result, 5)
})
Arrange–Act–Assert: every test sets up, does one thing, and checks one outcome
The AAA pattern keeps tests readable and focused. **Arrange** the inputs and any preconditions, **Act** by calling the function or endpoint under test, and **Assert** that the result matches expectations. A test should verify one logical behaviour — if you find yourself with many unrelated assertions, that's usually two tests trying to be one. Name tests by the behaviour they verify ("returns 404 when the user does not exist"), not by the function name.
The testing vocabulary

Term

Meaning

Test runner

Executes tests and reports results (node:test, Jest, Mocha)

Assertion

A check that throws if a condition is false (assert, expect)

Suite

A group of related tests (describe block)

Fixture

Known data/state a test runs against

Mock / Stub / Spy

Stand-ins for real dependencies (mocking)

Coverage

How much of your code the tests exercise (coverage)

What this section covers

Page

Topic

Unit, integration, E2E — the test pyramid

The built-in runner — zero dependencies

The batteries-included framework

The flexible, composable classic

Testing HTTP endpoints

Isolating code from its dependencies

Measuring and interpreting coverage

Writing tests first

A good test suite is fast, isolated, and deterministic
Flaky and slow tests get ignored — and an ignored test suite provides no safety
The most common way a test suite fails its purpose isn't missing tests — it's tests that are **flaky** (pass sometimes, fail others, usually due to timing, shared state, or real network calls) or **slow** (so they're skipped during development). A flaky test trains the team to ignore failures; a slow suite doesn't get run. Aim for tests that are deterministic (same input → same result every time), isolated (no dependency on other tests' state or order), and fast (the unit tests run in milliseconds). [Mocking](/nodejs/mocking) external dependencies is key to all three.
Next
Understand the test pyramid and what to test at each level: [Unit, Integration & E2E Tests](/nodejs/types-of-tests).