Unit tests skip the middleware stack, which is where most bugs live: a parser you forgot to mount, a route ordered after the catch-all, a header the error handler drops. Supertest 14,405 (npm 2,036 install --save-dev supertest) runs the real stack: hand it your app object and it calls app.listen(0) on an ephemeral port, issues a genuine request, and closes the server when the assertions finish.
import { test, describe, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import request from 'supertest';
import { createApp } from '../src/app.js';
import { makeStore } from '../src/store.js';
describe('Bookshelf API', () => {
let app;
beforeEach(() => { app = createApp({ store: makeStore() }); }); // fresh data per test
test('GET /healthz answers 200 with a JSON body', async () => {
const res = await request(app).get('/healthz').expect('Content-Type', /json/).expect(200);
assert.deepEqual(res.body, { status: 'ok' });
});
test('GET /api/books/:id returns 404 for a missing book', async () => {
await request(app).get('/api/books/9999').expect(404, { error: { code: 'not_found' } });
});
test('POST /api/books creates and echoes a Location header', async () => {
const res = await request(app).post('/api/books')
.send({ title: 'Persuasion', author: 'Jane Austen' }).expect(201);
assert.equal(res.headers.location, '/api/books/3');
await request(app).get(res.headers.location).expect(200); // follow it
}); });✔ GET /healthz answers 200 with a JSON body (23.5511ms) ✔ GET /api/books/:id returns 404 for a missing book (5.3863ms) ✔ POST /api/books creates and echoes a Location header (22.6823ms) ℹ tests 3 ℹ pass 3 ℹ fail 0
.expect() is overloaded: a number asserts the status, a string or regular expression after a header name asserts that header, and a second argument asserts the body by deep equality. .send(object) sets Content-Type: application/json. Add request(app).get('/nope').expect(404, { error: { code: 'no_route' } }) too: that catches a catch-all registered in the wrong place. Now comment out app.use(express.json()) and one test fails:
✖ POST /api/books creates and echoes a Location header (5.2751ms)
Error: expected 201 "Created", got 422 "Unprocessable Entity"
at TestContext.<anonymous> (file:///.../test/books-api.test.js:19:61)
ℹ tests 3 ℹ pass 2 ℹ fail 1Without the parser req.body is undefined, the guard returns 422, and the store is never reached.