Node.js cookbook · Beginner
Test an Express API with node:test and supertest
Write fast API tests without a separate test framework, including auth and validation cases.
Approach
Export the Express app separately from server.listen() so tests can call it in-process with supertest. Use the built-in node:test runner and node:assert, reset the test database between tests, and cover success, validation, auth and not-found cases.
Solution
// app.test.ts (run: node --test, or tsx --test for TypeScript)
import { after, before, beforeEach, describe, it } from "node:test";
import assert from "node:assert/strict";
import request from "supertest";
import { app } from "../src/app.js";
import { db, resetDb } from "./helpers/db.js";
describe("POST /api/orders", () => {
before(async () => { await db.migrate.latest(); });
beforeEach(async () => { await resetDb(); });
after(async () => { await db.destroy(); });
it("creates an order for an authenticated user", async () => {
const res = await request(app)
.post("/api/orders")
.set("Authorization", `Bearer ${await testToken({ sub: "user-1" })}`)
.send({ items: [{ sku: "A1", qty: 2 }] })
.expect(201);
assert.equal(res.body.items.length, 1);
});
it("rejects invalid bodies with 422", async () => {
const res = await request(app)
.post("/api/orders")
.set("Authorization", `Bearer ${await testToken({ sub: "user-1" })}`)
.send({ items: [] })
.expect(422);
assert.equal(res.body.errors[0].field, "body.items");
});
it("requires authentication", async () => {
await request(app).post("/api/orders").send({ items: [] }).expect(401);
});
});
Pitfalls
- Starting a real server on a fixed port in tests (port conflicts).
- Tests sharing database state.
- Mocking the database driver instead of using a test database.
Variations
- Vitest/Jest if you prefer their mocking and watch modes.
- Fastify's
app.inject()for in-process requests.
Further reading: Node.js: Test runner
Reference: https://nodejs.org/api/test.html
More Node.js recipes
- Add health and readiness endpoints to a Node.js service
- Add JWT authentication to an Express API
- Cache expensive lookups in Redis from Node.js
- Call external APIs with timeouts and retries in Node.js
- Centralize error handling in Express 5
- Hash passwords and implement login safely in Node.js
- Implement cursor pagination with PostgreSQL in Node.js
- Offload CPU-heavy work with worker threads