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.
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
| Level | Speed | Confidence | Example |
|---|---|---|---|
| Unit | Fast | Narrow | Services, utilities |
| Integration | Medium | Wider | Routes with real DB |
| End-to-end | Slow | Widest | Playwright 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 →