Testing Express

Mocha, Jest, Vitest — Pick One and Write Tests

Testing Express

Express apps are easy to test if you separate the app from the listen() call.

3 min read Level 1/5 #express#testing#vitest
What you'll learn
  • Set up Vitest for an Express app
  • Test pure functions and services
  • Plan the test pyramid

The single biggest predictor of testability in Express: does buildApp() exist as a separate function from listen()?

Setup

npm install --save-dev vitest supertest

package.json:

{
  "scripts": {
    "test":      "vitest",
    "test:run":  "vitest run"
  }
}

Test Pyramid

LevelSpeedConfidenceExample
UnitFastNarrowServices, utilities
IntegrationMediumWiderRoutes with real DB
End-to-endSlowWidestPlaywright on the full app

Most tests should be unit (services, validation, business logic). A modest number of integration tests via SuperTest (next lesson). A few e2e tests for critical flows.

Testing Pure Functions

The easiest wins — services that don’t import Express:

// src/services/cart.js
export function computeTotal(items, { discount = 0 } = {}) {
  const subtotal = items.reduce((s, i) => s + i.price * i.qty, 0);
  return Math.max(0, subtotal - discount);
}
// tests/cart.test.js
import { describe, it, expect } from "vitest";
import { computeTotal } from "../src/services/cart.js";

describe("computeTotal", () => {
  it("sums items", () => {
    expect(computeTotal([{ price: 10, qty: 2 }])).toBe(20);
  });

  it("applies a discount", () => {
    expect(computeTotal([{ price: 10, qty: 2 }], { discount: 5 })).toBe(15);
  });

  it("never goes negative", () => {
    expect(computeTotal([], { discount: 5 })).toBe(0);
  });
});

Pure functions = trivial tests.

Testing With Mocked DB

For services that hit the database:

import { vi, test, expect, beforeEach } from "vitest";
import * as users from "../src/services/users.js";

vi.mock("../src/db/client.js", () => ({
  db: { users: { findByEmail: vi.fn(), create: vi.fn() } },
}));

import { db } from "../src/db/client.js";

beforeEach(() => vi.clearAllMocks());

test("creates a user", async () => {
  db.users.findByEmail.mockResolvedValue(null);
  db.users.create.mockResolvedValue({ id: 1, email: "a@b.c" });

  const user = await users.create({ email: "a@b.c", password: "secret", name: "A" });
  expect(user.email).toBe("a@b.c");
});

Mock just enough to drive the code path.

Coverage

npm install --save-dev @vitest/coverage-v8
npx vitest run --coverage

Targets that work in practice: 80%+ on services, 50%+ overall. Don’t chase 100% — it pushes you to test trivial getters.

SuperTest →