Lab 14: Testing with Node.js Test Runner
Objective
Write unit tests, integration tests, and use mocking with Node.js's built-in node:test module and the assert library — no external dependencies required.
Background
Testing is how professional developers prove their code works and catch regressions before production. Node.js 18+ ships with a built-in test runner (node:test) and assertion library (node:assert) that eliminate the need for external frameworks in many cases. You'll also see patterns from Jest/Vitest since they share the same mental model. Every function you've written in previous labs should have tests.
Time
45 minutes
Prerequisites
Labs 01–07 (Functions, OOP, Error Handling)
Lab 13 (Functional Programming — functions to test)
Tools
Node.js 20 LTS
Docker image:
innozverse-js:latest
Lab Instructions
Step 1: Your First Test — assert and node:test
💡
assert/strictuses strict equality (===) for all comparisons. Always prefer it over the non-strict version.assert.throws()verifies that a function throws — passing the call as a lambda, not calling it directly.
📸 Verified Output:
Step 2: Organizing Tests — describe Blocks
💡
describegroups related tests under a label. This creates hierarchy in output and allowsbeforeEach/afterEachhooks scoped to the group. Each test creates its ownStackinstance — tests must be independent.
📸 Verified Output:
Step 3: beforeEach, afterEach Hooks
Set up and tear down test state without repeating code.
💡
beforeEachruns before every single test in the describe block. It ensures tests don't share state — test A's mutations can't affect test B. This isolation is the #1 rule of testing: each test must be independent and order-independent.
📸 Verified Output:
Step 4: Testing Async Code
Testing Promises, async/await, and error rejection.
💡 Async tests must be
asyncfunctions and you mustawaitinside them.assert.rejects()is the async equivalent ofassert.throws()— it awaits the rejected Promise and checks the error. Forgettingawaitmakes tests pass trivially (they complete before the Promise resolves).
📸 Verified Output:
Step 5: Mocking — Replace Dependencies
Use node:test mock utilities to replace dependencies with controlled test doubles.
💡 Mocking replaces real dependencies (databases, email servers, HTTP clients) with controlled fakes. This lets tests run fast (no real network calls), reliably (no flaky external services), and in isolation.
mock.fn()tracks every call, its arguments, and return values.
📸 Verified Output:
Step 6: Test Coverage — What to Test
💡 Test every branch. If code has an
if/else, test both paths. If it throws, test the throw. A good rule: if you can delete a line of production code and all tests still pass, you have a missing test. Aim for branch coverage, not just line coverage.
📸 Verified Output:
Step 7: Test-Driven Development (TDD) — Red-Green-Refactor
Write failing tests first, then implement the code to make them pass.
💡 TDD discipline: Write the test, run it (RED — it fails), write minimum code (GREEN — it passes), clean up (REFACTOR). The test becomes your specification. This forces you to think about the API and edge cases before implementation.
📸 Verified Output:
Step 8: Running Tests — CLI and Watch Mode
💡
assert.deepEqualvsassert.equal: Arrays and objects are reference types —[1,2] === [1,2]isfalse(different objects). UsedeepEqualfor structural comparison.equaluses===which is fine for primitives.
📸 Verified Output:
Verification
Expected: All 4 RateLimiter tests pass.
Common Mistakes
Not await-ing async tests
Async test functions must be async and awaited inside
assert.equal on arrays/objects
Use assert.deepEqual for structural comparison
Shared state between tests
Use beforeEach to create fresh instances
Not testing error paths
Every throw needs a corresponding assert.throws test
Testing implementation, not behavior
Test what functions return/do, not how they do it
Summary
You've written tests using Node's built-in node:test runner — unit tests, grouped describe blocks, beforeEach/afterEach hooks, async test handling, mocking with mock.fn(), branch coverage strategy, and TDD red-green-refactor. These skills apply directly to Jest, Vitest, and Mocha — they all share the same mental model.
Further Reading
Jest docs — the most popular JS testing framework
Last updated
