The Testing Pyramid
The pyramid is a mental model for balancing test types. A wide base of fast unit tests supports fewer, slower integration and E2E tests on top. Inverting the pyramid produces a slow, expensive test suite.
Unit Testing
Unit tests verify one function or module in complete isolation. They are the fastest feedback loop available and should cover both happy paths and edge cases.
import { calculateDiscount } from "./pricing";
describe("calculateDiscount", () => {
it("applies 10% for premium users", () => {
expect(calculateDiscount(100, "premium")).toBe(90);
});
it("returns full price for standard users", () => {
expect(calculateDiscount(100, "standard")).toBe(100);
});
it("throws on negative price", () => {
expect(() => calculateDiscount(-1, "premium")).toThrow();
});
});Integration Testing
Integration tests verify that multiple components — routes, services, and databases — work correctly together. They catch contract mismatches that unit tests cannot see.
describe("POST /api/users", () => {
it("creates a user and returns 201", async () => {
const response = await request(app)
.post("/api/users")
.send({ name: "Alice", email: "alice@example.com" });
expect(response.status).toBe(201);
expect(response.body).toMatchObject({ name: "Alice" });
});
it("returns 409 when email already exists", async () => {
await createUser({ email: "alice@example.com" });
const response = await request(app)
.post("/api/users")
.send({ email: "alice@example.com" });
expect(response.status).toBe(409);
});
});E2E Testing
E2E tests drive a real browser through complete user flows. They provide the highest confidence that the product works end-to-end, but are the slowest and most brittle layer — use them sparingly for critical paths.
import { expect, test } from "@playwright/test";
test("user can log in and see dashboard", async ({ page }) => {
await page.goto("/login");
await page.fill("[name=email]", "user@example.com");
await page.fill("[name=password]", "password");
await page.click("[type=submit]");
await expect(page).toHaveURL("/dashboard");
await expect(page.getByRole("heading")).toContainText("Welcome");
});| Tool | Best For |
|---|---|
| Playwright | Modern apps, multi-browser, CI-friendly |
| Cypress | Great DX, real-time reload, component testing |
| Selenium | Legacy support, broad language bindings |
| WebdriverIO | Mobile + desktop cross-platform testing |