DEV Community

Young Gao
Young Gao

Posted on

Testing Express APIs: Unit Tests, Integration Tests, and When to Use Each

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);
  });
});
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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)