Node.js Foundations·From script to service

Testing HTTP handlers

Separate handler logic from storage so tests run fast without real network calls.

Intermediate 13 min read Lesson 4 of 300 Challenge included
What you will be able to do
  • Write handlers that receive their data store as an argument.
  • Unit-test the handler with a fake store and no network.
  • Reserve real HTTP calls for a small number of integration tests.
  • Return plain data and status codes so they are easy to assert.

In plain English

A handler is the function that turns a request into a response. When a handler reaches into a database directly, every test needs a live database and the network, which makes tests slow and flaky. Instead, pass the storage in as a dependency so a test can supply a fake store that returns fixed data instantly. A unit test then checks only the handler logic: what status code and body it produces. Network-level tools such as supertest or fetch are useful for a few integration tests, but they should not be the only safety net.

Step by step

  1. 1Move business logic into a handler that accepts its store.
  2. 2Create a fake store that returns predictable data.
  3. 3Assert the status code and body the handler produces.
  4. 4Add a separate integration test for the real store when needed.
handler.test.tsTypeScript
import assert from 'node:assert/strict'
import { test } from 'node:test'
import { makeUserHandler, type User, type UserStore } from './handler'

const fakeStore: UserStore = {
  async findById(id: string): Promise<User | null> {
    return id === '1' ? { id: '1', name: 'Ada' } : null
  },
}

test('returns the user when it exists', async () => {
  const handler = makeUserHandler(fakeStore)
  const result = await handler('1')
  assert.equal(result.status, 200)
  assert.deepEqual(result.body, { id: '1', name: 'Ada' })
})

test('returns 404 when the user is missing', async () => {
  const handler = makeUserHandler(fakeStore)
  const result = await handler('2')
  assert.equal(result.status, 404)
})

Common mistakes

  • Testing against a live database makes tests slow and dependent on network availability.
  • Hard-coding the database import inside the handler makes the logic impossible to test in isolation.
  • A test that only passes sometimes teaches the team to ignore failing tests.
  • Skipping the missing-resource case leaves the 404 path untested.
Your turn

Test a 404 path

Easy

Write a fake store whose findById always returns null, then assert that makeUserHandler returns a 404.

JAVASCRIPT · starter
const handler = makeUserHandler(fakeStore)
const result = await handler('missing')
Check your understanding before moving on.
Knowledge checkQuestion 1

Why pass a store into a handler as a dependency?

Select an answer to continue
Study tools

Make it yours.

Keep a private note for this lesson, then turn the key ideas into flashcards you can revisit.

Sign in to save notes and flashcards.Both are private to your account and stay with your progress.
Sign in
Finished reading?

Lock in what you just learned.

Mark this lesson complete to save your place and move one step closer to the certificate.

Lesson 4 of 300 · 0% of course