Testing Pyramid

Overview

The Testing Pyramid is a guideline for the distribution of different types of automated tests in a software project. It suggests that you should have a large number of low-level tests and a small number of high-level tests to ensure a fast, reliable, and cost-effective test suite.

Core Concepts

  • Unit Tests (Base):
    • Scope: Test a single function or class in isolation.
    • Characteristics: Extremely fast, highly deterministic, easy to write.
    • Goal: Verify that a small “unit” of logic works as expected.
  • Integration Tests (Middle):
    • Scope: Test the interaction between two or more components (e.g., a service and its database).
    • Characteristics: Slower than unit tests, may require external dependencies (mocks or real test databases).
    • Goal: Ensure that different parts of the system work together correctly.
  • End-to-End (E2E) Tests (Top):
    • Scope: Test the entire application from the user’s perspective (e.g., using a browser to log in and checkout).
    • Characteristics: Slowest, most brittle, most expensive to maintain.
    • Goal: Verify that the “critical paths” of the application are functional.

Code Examples

// Unit Test (Jest)
test('add should return sum of two numbers', () => {
    expect(add(1, 2)).toBe(3);
});

// Integration Test (Conceptual)
test('UserService.saveUser should persist user to DB', async () => {
    const user = { name: "Bob" };
    await userService.saveUser(user);
    const saved = await db.findUser("Bob");
    expect(saved).toEqual(user);
});

// E2E Test (Cypress/Playwright)
it('should allow a user to log in', () => {
    cy.visit('/login');
    cy.get('#username').type('admin');
    cy.get('#password').type('password');
    cy.get('#submit').click();
    cy.url().should('include', '/dashboard');
});

Use Cases

  • Regression Testing: Running the pyramid on every commit to ensure no new bugs were introduced.
  • Confidence: Providing developers with the confidence to refactor code without breaking existing functionality.
  • Fast Feedback Loop: Unit tests provide instant feedback, while E2E tests provide the final seal of approval.

Gotchas

  • The “Ice Cream Cone” Anti-pattern: Having too many E2E tests and not enough unit tests. This leads to a slow, flaky test suite that is hard to debug.
  • Over-Mocking: Mocking too much in integration tests can lead to a “green” test suite that fails in production because the real interaction was different.

Related Notes