BugMojoBugMojoBugMojo
FeaturesPricingBlogHelpAbout
Add to ChromeLog inGet started
BugMojoBugMojo

Bug reports that actually help fix bugs — capture, replay, share.

A product of Softech Infra.

Product

  • Features
  • Pricing
  • Browser extension
  • Get started
  • Log in

Resources

  • Help & guides
  • Blog
  • Compare
  • Glossary

Company

  • About
  • Contact
  • Security
  • Privacy
  • Terms
  • Sitemap
© 2026 BugMojo. All rights reserved.
AllGuidesEngineeringPlaybooksCompareGlossaryAlternativesBy roleBug tracking by framework
  1. Home
  2. Blog
  3. Guides
  4. Unit vs Integration vs E2E Testing (The Testing Pyramid)
Guide

Unit vs Integration vs E2E Testing (The Testing Pyramid)

Unit, integration, and end-to-end tests each check the system at a different zoom level, trading speed for realism. The Testing Pyramid, from Mike Cohn and Martin Fowler, tells you how to mix them: many fast unit tests at the base, fewer integration, a few slow E2E on top.

ManviManvi·Jul 16, 2026·9 min read
Guides
Isometric line-art testing pyramid of three stacked layers — many fast unit tests at the base, integration in the middle, few slow end-to-end tests on top, lime on a dark canvas
TL;DR
  • Three levels, three zoom settings. Unit tests check one function in isolation; integration tests check a few units wired together; end-to-end (E2E) tests drive the whole app through the real UI.
  • Each trades speed for realism. Unit tests run in milliseconds but can't see integration bugs; E2E tests catch real user-journey failures but are slow and brittle.
  • The Testing Pyramid tells you the mix: many unit tests at the base, fewer integration in the middle, a few E2E at the top.
  • Avoid the 'ice cream cone' — a suite dominated by slow, flaky E2E tests that nobody trusts.
  • The pyramid is a heuristic, not a law. For some front-end apps the 'testing trophy' is a fair alternative.

Ask three engineers what a "test" is and you may get three different pictures: one imagines a tiny function called with a fake input, one imagines a service talking to a real database, and one imagines a browser clicking through a checkout. They're all right — they're just describing different test levels. Unit, integration, and end-to-end tests aren't competing philosophies; they're three zoom settings on the same system, and each one sees bugs the others are blind to.

The hard part isn't understanding the levels individually — it's knowing how many of each to write. Test at too fine a grain and you'll ship code where every unit works but nothing works together; test only at the coarsest grain and you'll drown in slow, flaky suites that take twenty minutes to tell you a button moved. This guide defines each level by what it tests, how fast it is, what it catches, and what it misses, then uses the Testing Pyramid — the heuristic popularised by Mike Cohn and sharpened by Martin Fowler — to tell you how to balance them.

The three levels of testing

Unit tests verify one function or component in isolation with its dependencies mocked; integration tests verify several real units working together, like a service and its database; end-to-end tests drive the whole running app through the real UI as a user would. Scope, realism, runtime, and fragility all grow as you move up.

Unit tests — one piece, in isolation

A unit test exercises the smallest testable piece of code — a single function, method, or component — with its dependencies replaced by mocks or stubs so nothing else can interfere. Because it touches no network, no database, and no file system, it runs in milliseconds, which means you can run thousands of them on every save and every commit.

What it catches: logic bugs. Off-by-one errors, a wrong conditional, a mishandled null, a currency-rounding mistake — anything that lives inside one unit's own reasoning. When a unit test fails, it points at almost the exact line, because nothing else was in scope. What it misses: everything about how units combine. A unit test can prove your calculateTotal() is correct and your chargeCard() is correct while the two of them pass money in the wrong units to each other, and no unit test will ever notice. Example tooling: Jest, Vitest, JUnit, pytest, Go's built-in testing package.

Integration tests — units working together

An integration test takes several real units and checks that they cooperate across the seams between them. Instead of mocking the database, you run against a real (often ephemeral) one and assert that your repository layer actually reads back what it wrote. Instead of stubbing the API, you mount a component together with its real data-fetching layer and check the two speak the same shape. The units are no longer alone; the test is about the contract where they meet.

What it catches: wiring and contract bugs — the mismatches that unit tests structurally cannot see. A renamed database column, a serializer that emits snake_case while the client expects camelCase, a transaction that isn't actually committing, an API that returns 200 with an empty body. These are the failures that live between correct components. The cost: integration tests are slower (they do real I/O) and need real setup — a database to spin up, fixtures to seed, state to tear down. What it misses: the full user journey through the actual interface. Example tooling: Testcontainers, Supertest, React Testing Library against a real API layer, Spring's @SpringBootTest.

End-to-end (E2E) tests — the whole app, as a user

An end-to-end test drives the entire running application the way a real person would: it opens a browser, clicks buttons, fills forms, and asserts on what actually appears on screen — with every real layer underneath, from the UI down through the API, services, and database. Nothing is mocked. It's the only level that answers the question that actually matters to your users: can someone complete this journey?

What it catches: real user-journey failures that no lower level can — a broken route, a button that renders but doesn't wire up its handler, a login flow that fails only when the real session cookie and the real redirect meet. If sign-up is broken end to end, an E2E test is what tells you before your customers do. The price is steep: E2E tests are the slowest (seconds to minutes each) and by far the most brittle, because they depend on timing, animations, network latency, test data, and sometimes third-party services — any of which can make a test fail without a real bug behind it. That flakiness is why E2E tests are the ones teams fight with most; see our guide on debugging flaky tests. Example tooling: Playwright, Cypress, Selenium.

The three levels side by side

FeatureUnitIntegrationE2E
ScopeOne function or component, in isolationA few real units wired togetherThe whole app through the real UI
SpeedMilliseconds — run constantlySeconds — real I/OSeconds to minutes — slowest
What it catchesLogic bugs inside one unitContract / wiring bugs between modulesBroken real user journeys
BrittlenessVery stableModerate — needs real setupHigh — timing, network, flake
How many you wantMany (the base)Fewer (the middle)Few (the tip)
Example toolsJest, Vitest, JUnit, pytestTestcontainers, Supertest, RTLPlaywright, Cypress, Selenium
The same system at three zoom levels — scope, speed, and fragility all climb as you move from unit to E2E.

The Testing Pyramid: how to balance them

Knowing the three levels is only half the picture; the other half is proportion. The Testing Pyramid is the mental model for that. Picture a triangle divided into three horizontal bands. The wide base is unit tests — you want many of them, because they're fast and stable, so most of your coverage should come from here. The narrower middle is integration tests — fewer, covering the risky seams where modules meet. The small tip is E2E tests — few, covering only your most critical end-to-end journeys.

The shape isn't arbitrary; it's an economic argument. As you climb, every test gets slower to run, more brittle, and more expensive to maintain — so you want the bulk of your safety net woven from the cheap, reliable threads at the bottom, and only a handful of the expensive full-system checks at the top. The practical rule of thumb: push every check as far down the pyramid as it can meaningfully go. If a bug can be caught by a fast unit test, don't spend a slow, flaky E2E test to catch it.

1The pyramid predates modern web apps — Mike Cohn drew it in Succeeding with Agile (2009), and Martin Fowler has maintained the canonical write-up ever since. The layer names vary by team; the shape is the durable idea.

The ice cream cone anti-pattern

Turn the pyramid upside down and you get the ice cream cone: a huge scoop of E2E tests on top, a thin layer of integration below, and almost no unit tests at the bottom — often topped with a big blob of slow manual testing. It's what teams drift into when they test only through the UI because that's the layer they can see.

Common mistake

Why the ice cream cone hurts. Because E2E tests are the slowest and flakiest level, a suite dominated by them takes ages to run and fails intermittently for reasons that have nothing to do with real bugs. The team learns that red doesn't necessarily mean broken — so people start re-running, muting, and finally ignoring failures. An untrusted test suite is worse than none: it costs time and gives false confidence. Google's testing team made exactly this case in "Just Say No to More End-to-End Tests": the fix is to move coverage down the pyramid, catching most bugs where they're cheap and stable to catch.

A heuristic, not a law

The pyramid is a guideline, not a physical constant, and it pays to hold it loosely. The boundaries between levels are genuinely fuzzy — Martin Fowler notes that even "unit test" means different things to different people (the solitary unit test mocks all collaborators; the sociable one lets real ones participate), so where a test sits on the pyramid is partly a matter of definition.

For some codebases a different shape fits better. The testing trophy, popular in front-end circles, deliberately fattens the integration layer — the argument being that for UI-heavy apps, tests that render a component with its real hooks and data give the best confidence-per-second, so integration, not unit, deserves the widest band. That's a reasonable position, not a heresy. The through-line across every model is the same: favour fast, reliable tests over slow, brittle ones, and spend your slow-test budget only where it buys real-world confidence you can't get any cheaper. Use the pyramid to think, not to obey.

When an E2E test fails, capture the failure

Here's where the top of the pyramid meets daily reality. An E2E test is the one most likely to fail in CI — and also the hardest to debug, precisely because it spans every layer. A red line in your pipeline tells you a journey broke, but not why. Was it a genuine bug, or was it flake? What was on the screen? What did the console say? Which network request came back wrong? Without that, you're stuck re-running the job locally and hoping it fails the same way twice — which, for a flaky E2E test, it often won't.

That's the gap BugMojo closes. When an E2E test fails, a captured session replay of the exact run — plus the console logs and network requests at the moment it broke — turns a bare red CI line into a reproducible bug. Instead of a stack trace and a shrug, a developer (or an AI agent over MCP) opens the failure and watches the journey break: the click that did nothing, the request that 500'd, the error the browser swallowed. The flakiest, most expensive layer of your pyramid becomes the one you can actually diagnose. For the level below, on framing checks as repeatable experiments, see how to write test cases; to fit all three levels into a release, see what a test plan is and how a smoke suite differs from a regression suite.

Key takeaway

Unit, integration, and E2E tests are three zoom levels on one system: unit checks a function in isolation, integration checks units wired together, E2E checks the whole app as a user. Each trades speed for realism. Balance them with the Testing Pyramid — many fast unit tests, fewer integration, few slow E2E — and avoid the ice-cream-cone flip. Treat it as a heuristic, push every check as low as it can meaningfully go, and when an E2E test fails, capture the replay so a red line becomes a reproducible bug.

⁓ ⁓ ⁓
Turn failing E2E tests into reproducible bugs

When an end-to-end test goes red in CI, capture the session replay, console, and network in one artifact — attached and ready for a developer or an AI agent over MCP.

Install the free extension

Frequently asked questions

Frequently asked questions

Sources

  1. Martin Fowler — 'The Practical Test Pyramid', the canonical explanation of test levels and why the distribution matters — martinfowler.com (2026)
  2. Martin Fowler — 'UnitTest', on what actually counts as a unit test and the sociable-vs-solitary distinction — martinfowler.com (2026)
  3. Google Testing Blog — 'Just Say No to More End-to-End Tests', the case for fewer E2E tests and more unit coverage — Google Testing Blog (2015)
  4. ISTQB Glossary — the standard definitions of 'test level', 'component testing', 'integration testing', and 'system testing' — ISTQB (2026)
Share:
Manvi
Manvi· QA Tester

Manvi is a Quality Assurance Tester with three years of experience. For her, quality is not just about finding bugs — it is about ensuring the best possible experience for every user.

On this page

  • The three levels of testing
  • Unit tests — one piece, in isolation
  • Integration tests — units working together
  • End-to-end (E2E) tests — the whole app, as a user
  • The three levels side by side
  • The Testing Pyramid: how to balance them
  • The ice cream cone anti-pattern
  • A heuristic, not a law
  • When an E2E test fails, capture the failure

Get bug-tracking insights, weekly.

Engineering deep-dives, QA playbooks, and honest tool comparisons. No spam — unsubscribe in one click.