The Testing Pyramid: What Is It Really?

If you've read The testing pyramid for frontend — how to distribute your test budget by Mira Halsted, or any of the testing articles in her series, you've seen the pyramid mentioned as the framework for deciding how many tests to write at each level.
And if you're relatively new to testing, you might have thought: I understand there are different kinds of tests. I don't fully understand why a pyramid shape, why those ratios, or what I should actually be writing for the components I build every day.
This article answers all of that — with a real set of components and tests you can follow from scratch.
The problem testing solves
Before the pyramid, let's establish why we test at all.
You write a Button component. It works. You ship it. Three weeks later you change the styling and accidentally break the disabled state. A user submits a form twice because the disabled button wasn't actually preventing clicks. You find out from a bug report, not from your own testing.
Tests are a safety net. They tell you when something you changed broke something else. The earlier you catch a break — ideally before it ships — the cheaper it is to fix.
The testing pyramid is a framework for building that safety net efficiently. Not all tests are created equal in terms of cost, speed, and what they catch. The pyramid tells you which tests to write more of and which to write fewer of — and why.
The three layers
Think of the pyramid as a triangle with three horizontal bands:
Unit tests — wide base, many tests, fast, cheap. Integration tests — middle band, moderate tests, medium speed. End-to-end (E2E) tests — narrow top, few tests, slow, expensive.
The shape communicates the ratio: write lots of unit tests, a moderate number of integration tests, and a small number of E2E tests.
Let's define each layer concretely before talking about why.
Layer 1: unit tests
A unit test tests one thing in isolation — a function, a hook, a component — with everything it depends on either mocked or removed from the picture.
The key word is isolation. If your formatDate function breaks, your unit test for formatDate should fail. Nothing else should fail. The test is surgical.
Two tests. One function. The tests run in milliseconds. If formatDate breaks — because someone changed the locale, the format options, or the function logic — these tests fail instantly.
What unit tests are good at:
- Catching logic bugs in pure functions
- Testing edge cases quickly (empty string, zero, null, extreme values)
- Testing hooks in isolation with
renderHook - Running fast — a thousand unit tests in under two seconds
What unit tests can't catch:
- Whether two correctly-written functions work together correctly
- Whether a component renders the right output in a real browser context
- Whether a user flow actually works end-to-end
Layer 2: integration tests
An integration test tests multiple units working together — without mocking everything away, but without a real browser or real network.
For React components, integration tests are what React Testing Library is designed for. You render a component (which renders its children, which might call hooks, which might call utilities), you interact with it the way a user would, and you assert the result.
This test doesn't mock formatDate. It tests the component as a unit that includes formatDate. If formatDate breaks, this test also fails — and that's correct. The integration test covers the seam between the component and its dependency.
What integration tests are good at:
- Testing components that use hooks, utilities, and child components together
- Catching bugs at the seams between units
- Testing user interactions (clicking, typing, form submission)
- Testing conditional rendering and state changes
What integration tests can't catch:
- Whether the component looks right in a real browser
- Whether a multi-page user flow works
- Whether real API calls succeed
Layer 3: end-to-end tests
An E2E test drives a real browser through a real user journey. It opens a URL, clicks buttons, fills forms, and asserts outcomes — the way an actual user would.
This test covers something neither unit nor integration tests can: the full path from authentication to published article, including real routing, real form submission, real database writes.
What E2E tests are good at:
- Verifying critical user journeys work end to end
- Catching integration issues between frontend and backend
- Testing things that only exist in a real browser (navigation, real network requests)
What E2E tests are bad at:
- Speed — a single E2E test can take 10–30 seconds
- Reliability — they're sensitive to network conditions, timing, and UI changes
- Diagnosing failures — when they fail, it's not always obvious why
Why the pyramid shape
Now the pyramid makes sense. You want:
Lots of unit tests because they're fast, cheap, and surgical. A codebase with 500 unit tests that run in 3 seconds gives you continuous feedback as you code.
A moderate number of integration tests because they catch the bugs that unit tests miss — the seam bugs. But they're slower (10–100ms each instead of 1–2ms) and more complex to write.
A few E2E tests because they catch what integration tests miss — but they're 100x slower and 10x harder to maintain. You can't afford to have 500 of them. You pick your most critical user journeys: login, checkout, the core action your product is built around.
The pyramid is a budget allocation strategy. If you have an hour to write tests, the pyramid says: spend 60% of that hour on unit tests, 30% on integration tests, and 10% on E2E tests. You'll catch the most bugs per hour of testing invested.
Building the complete test suite for a real component
Here's a SubscribeForm component — a simple email subscription form — tested at all three levels. This is what the pyramid looks like in practice.
Unit tests — the isValidEmail function
Five tests. Pure function. No React involved. Runs in under 1ms. If the validation regex breaks, these fail immediately.
Integration tests — the SubscribeForm component
Six integration tests. Each covers a user-facing behaviour. These take 20–50ms each — fast enough to run on every file save.
E2E test — the full subscription flow
One E2E test. It covers what the integration tests can't: real routing, real API call, real database interaction. It runs in 5–10 seconds. You don't write ten of these — you write one for the happy path and trust the integration tests to cover the edge cases.
The ratio in practice
For this SubscribeForm:
- 5 unit tests — the validation function, all edge cases
- 6 integration tests — all user-facing behaviours
- 1 E2E test — the critical happy path
That's a 5:6:1 ratio — roughly the pyramid shape. If you added ten more edge cases to the validation function, you'd add ten unit tests, not ten integration tests. If you added a new user flow (unsubscribe), you'd add integration tests for that flow and one E2E test for the critical path.
The pyramid scales with the codebase.
Where this leads
Vitest and React Testing Library — Mira Halsted's How to set up Vitest in a Vite or Next.js project from scratch and Component testing with React Testing Library — the mental model that makes it click are the practical next steps for setting up the unit and integration layers.
Playwright — How to set up Playwright for end-to-end testing in a Next.js app covers the E2E layer setup and the patterns that make E2E tests reliable rather than brittle.
The testing pyramid for frontend — Mira's closing article in the testing series, The testing pyramid for frontend — how to distribute your test budget, covers the strategic decisions that this article introduces through the lens of a real project with a real team.
This article was created for better understanding of Mira Halsted's testing series — particularly The testing pyramid for frontend and Component testing with React Testing Library. The SubscribeForm component above is yours to take — the test suite covers it at all three layers and gives you a starting point for the pattern in your own codebase.
Muhammad Athar is the founder and engineer behind DesignDev.io. He writes the "From the Builder" series — concept explainers triggered by the site's own articles, always grounded in something you can actually build.
Photo by Laurence Ziegler on Unsplash

