CodingNeed.

3 · Connect, test and build · 18 MIN

Write repeatable tests for the logic

A test should check behavior, including boundaries and failures.

Manual checks are useful but easy to forget. Node’s built-in test runner executes named tests and reports failures. Extract pure logic from HTTP handlers so it can be tested with simple values. deepEqual compares the structure of arrays and objects; equal suits primitive values. This example tests filtering and a zero limit. It also checks that the input array is unchanged. Add separate tests for each meaningful contract instead of copying the function’s implementation into the test.

Run node --test lesson.mjs in the terminal. Read each test name before its assertion. Add one edge case per test. Break the function, observe a red result, then restore it.

Before you start

Install Node.js 22.12+ (or a supported newer LTS). Create a folder named codingneed-node. Open a terminal in that folder. Save ONE lesson example as lesson.mjs and run node lesson.mjs. No npm dependencies are required. HTTP lessons keep running until you press Ctrl+C; stop the previous server before starting another.

File for this example: lesson.mjs

New words, explained

testRunner
A program that executes tests and reports their outcomes.
pureFunction
A calculation depending on its inputs without external side effects.
regression
A previously working behavior broken by a change.

Follow the example step by step

  1. Run node --test lesson.mjs in the terminal.
  2. Read each test name before its assertion.
  3. Add one edge case per test.
  4. Break the function, observe a red result, then restore it.

You are ready to move on when: All five behavior tests pass after restoring the implementation. An intentional filter bug causes a failure. Explain why helper and HTTP contracts can have different allowed inputs.

Read the example

import test from 'node:test';
import assert from 'node:assert/strict';
function findTopics(topics, query, limit) {
  return topics.filter(topic => topic.toLowerCase().includes(query.trim().toLowerCase())).slice(0, limit);
}
test('matches case and ignores surrounding spaces', () => {
  assert.deepEqual(findTopics(['React', 'SQL'], ' sql ', 2), ['SQL']);
});
test('zero limit returns nothing without mutating the list', () => {
  const topics = ['React'];
  assert.deepEqual(findTopics(topics, '', 0), []);
  assert.deepEqual(topics, ['React']);
});
Check the expected output
Run node --test lesson.mjs. Both named tests pass. This helper accepts a zero limit, unlike the previous HTTP endpoint’s stricter contract.

Your challenge

Add tests for an empty list, no matching topic and applying a limit after filtering. Deliberately introduce a bug and confirm one of your tests fails.

Solution cost: Discuss the operations in this small example; rendering and I/O costs depend on the host. time · Proportional to the example’s retained data. space

Common trap

Passing tests demonstrate the cases checked; they do not prove every possible input is correct.

Study the project implementation
import test from 'node:test';
import assert from 'node:assert/strict';
function findTopics(topics, query, limit) {
  return topics.filter(topic => topic.toLowerCase().includes(query.trim().toLowerCase())).slice(0, limit);
}
test('matches case and ignores surrounding spaces', () => {
  assert.deepEqual(findTopics(['React', 'SQL'], ' sql ', 2), ['SQL']);
});
test('zero limit returns nothing without mutating the list', () => {
  const topics = ['React'];
  assert.deepEqual(findTopics(topics, '', 0), []);
  assert.deepEqual(topics, ['React']);
});

Further reading: Official documentation

Next lesson: Project · A small searchable topics API

Essential cookies keep your account signed in. Optional analytics is not configured on this site. Your choice does not affect access to lessons.

Read the Privacy Policy. You can change this choice in the footer.