Testing Express APIs
On this page
Why HTTP Testing Needs a Tool
Testing HTTP endpoints by manually calling handlers misses middleware behavior, route matching, headers, and serialization. Supertest runs requests against your app without binding an actual port, keeping tests fast and deterministic.
Test the Contract, Not the Implementation
API tests should assert status codes, response shape, headers, and error codes. Avoid asserting exact formatting or internal logs. Treat the HTTP interface as the contract.
Example: Express + Supertest
import test from 'node:test';
import assert from 'node:assert/strict';
import request from 'supertest';
import { createApp } from '../src/app';
test('GET /health returns ok', async () => {
const app = createApp();
const res = await request(app).get('/health').expect(200);
assert.deepEqual(res.body, { ok: true });
});
Example: Validate Error Format
test('POST /users validates input', async () => {
const app = createApp();
const res = await request(app)
.post('/users')
.set('Content-Type', 'application/json')
.send({ email: 123 })
.expect(400);
assert.equal(res.body.error.code, 'VALIDATION_ERROR');
});
Production Tips
- Build the app in a factory function so tests can create isolated instances
- Use a dedicated test DB or mock repositories where appropriate
- Keep HTTP tests focused on contract-critical routes