Testing Express APIs: Unit Tests, Integration Tests, and When to Use Each
You have 200 unit tests. They all pass. You deploy. The API returns 500. Your tests mocked everything, including the bugs.
Integration Tests
import supertest from "supertest";
import { app } from "../src/app";
import { db } from "../src/db";
const request = supertest(app);
beforeAll(async () => { await db.migrate.latest(); });
afterEach(async () => { await db("users").truncate(); });
describe("POST /api/users", () => {
it("creates a user", async () => {
const res = await request
.post("/api/users")
.send({ name: "Alice", email: "alice@test.com" })
.expect(201);
expect(res.body.id).toBeDefined();
});
it("rejects duplicate email", async () => {
await request.post("/api/users").send({ name: "A", email: "a@t.com" });
await request.post("/api/users").send({ name: "B", email: "a@t.com" }).expect(409);
});
it("validates required fields", async () => {
await request.post("/api/users").send({}).expect(400);
});
});
When to Unit Test
Unit test pure business logic with no I/O:
function calculateDiscount(subtotal: number, percent: number): number {
return Math.round(subtotal * (percent / 100) * 100) / 100;
}
test("applies discount", () => {
expect(calculateDiscount(100, 20)).toBe(20);
});
Use a real test database, not mocks. Docker makes this easy. Run with --runInBand to avoid race conditions.
Part of my Production Backend Patterns series. Follow for more practical backend engineering.
Top comments (0)