Testing Express Applications
1. Installing Testing Libraries
Example: vitest + supertest
npm i -D vitest supertest @vitest/coverage-v8
# or:
npm i -D jest supertest @types/jest
# Native Node 20+:
node --test --experimental-test-coverage
2. Installing supertest Package
3. Creating Test Suites
Example: vitest describe/it
import { describe, it, expect, beforeAll, afterAll } from "vitest";
describe("GET /users", () => {
beforeAll(async () => { /* seed db */ });
afterAll(async () => { /* cleanup */ });
it("returns 200 + array", async () => { /* ... */ });
});
4. Writing Unit Tests
Example: Pure function test
import { parsePagination } from "../src/utils/pagination.js";
it("clamps limit to 100", () => {
expect(parsePagination({ query: { limit: 5000 } })).toMatchObject({ limit: 100 });
});
5. Writing Integration Tests
Example: Full app + DB
import request from "supertest";
import { app } from "../src/app.js";
it("creates and lists a user", async () => {
await request(app).post("/users").send({ email: "a@b.c" }).expect(201);
const { body } = await request(app).get("/users").expect(200);
expect(body.data).toHaveLength(1);
});
6. Using supertest for HTTP Requests
Example: Methods
await request(app).get("/users").expect(200);
await request(app).post("/users").send({ email: "x@y.z" }).expect(201);
await request(app).patch("/users/1").send({ name: "X" }).expect(200);
await request(app).delete("/users/1").expect(204);
Note: Pass
app directly to request(app) — supertest binds an ephemeral port automatically. No need to app.listen().7. Testing Route Handlers
Example: All verbs
describe("/users/:id", () => {
it("GET 404 when missing", async () => {
await request(app).get("/users/999").expect(404);
});
it("PUT 200 when valid", async () => {
await request(app).put("/users/1").send({ email: "z@z.z" }).expect(200);
});
});
8. Asserting Response Status
Example: Multiple assertions
const res = await request(app).get("/users/1");
expect(res.status).toBe(200);
expect(res.headers["content-type"]).toMatch(/json/);
9. Asserting Response Body
Example: Match shape
const { body } = await request(app).get("/users/1").expect(200);
expect(body).toMatchObject({ id: 1, email: expect.stringContaining("@") });
10. Mocking Database Calls
Example: vi.mock
import { vi } from "vitest";
vi.mock("../src/db.js", () => ({
db: { users: { findById: vi.fn().mockResolvedValue({ id: 1, email: "a@b.c" }) } }
}));
11. Setting Up Test Database
| Strategy | Notes |
|---|---|
| Testcontainers | Spin Postgres in Docker per test run |
| SQLite in-memory | Fast; not 1:1 with prod |
| Schema-per-worker | Parallel tests on shared Postgres |
| Truncate between tests | Isolation; faster than re-create |
12. Testing Middleware Functions
Example: Mount on tiny app
import express from "express";
import request from "supertest";
import { requireAuth } from "../src/middleware/auth.js";
it("rejects without token", async () => {
const app = express();
app.get("/x", requireAuth, (req, res) => res.json({ ok: true }));
await request(app).get("/x").expect(401);
});