Free ToolBy GitIntel

Unit vs Integration vs E2E Tests: How to Split Your Test Suite

Practical ratios, cost analysis, and the common dysfunctions that make test suites slow and expensive.

GitIntel tracks AI-generated code across your entire git history — giving every tool on this page the attribution layer that standard dev tooling misses.

GitIntel shows test coverage trends and AI-generated test patterns in your git history

The testing pyramid is the most repeated advice in software engineering, and the most frequently ignored. In practice, many codebases have the inverse: a thin layer of unit tests and a heavy E2E suite that takes 45 minutes to run. This is backwards, and it's expensive.

The test pyramid (Kent Beck, expanded by Martin Fowler) places fast, cheap unit tests at the base, integration tests in the middle, and E2E tests at the top. The rationale is economic: a unit test runs in milliseconds and pinpoints the exact failing line. An E2E test takes 2-5 seconds, requires a browser, a server, a database, and a test environment, and gives you a stack trace that might end at a network timeout.

Unit tests: test a single function, class, or module in isolation with all dependencies mocked or stubbed. They should be the majority of your test count — roughly 60-70% of total tests. Vitest and Jest run thousands of unit tests in under 10 seconds. The focus: pure functions, business logic, edge cases (empty arrays, null inputs, overflow values), and algorithm correctness. Don't unit test framework code.

Integration tests: test multiple units working together — a service method that hits a real database, a React component that renders with real API data (via MSW mocking), or a REST endpoint end-to-end through your middleware stack. These should be 20-30% of your test suite. They're slower than unit tests but faster than E2E, and they catch the contracts between modules that unit tests miss.

E2E tests: test complete user workflows in a real browser against a running application. Reserve these for critical paths: signup flow, payment checkout, core feature workflows. 5-15% of tests, targeting the workflows where a silent failure costs the most. E2E tests are expensive to write, maintain, and run — use them selectively.

The common dysfunction to avoid: teams that write 90% E2E tests because they don't trust unit tests. This is usually a symptom of untestable code (tight coupling, global state, no dependency injection). Fix the architecture before fixing the test ratio.

CI cost implication: a 1,000-test suite that's 70% unit/20% integration/10% E2E runs in under 5 minutes. The inverse (10% unit/20% integration/70% E2E) runs in 45-90 minutes and costs 10x more in CI minutes.

Frequently Asked Questions

What code coverage percentage should I target?

80% line coverage is a common target and good starting point. Above 80%, the marginal value per additional test drops while the maintenance cost grows. Coverage percentage doesn't measure test quality — 100% coverage is achievable with trivial assertions that catch nothing. Focus on covering your business logic and error paths completely, and accept lower coverage for glue code and configuration.

Should I test private methods?

Test behavior, not implementation. Private methods are implementation details — if they're covered by the public interface tests, you don't need to test them directly. If a private method is complex enough that you feel you must test it directly, that's a signal it should be extracted into a separate, public, well-named function.

How do I speed up a slow E2E test suite?

Four levers: parallelize across workers (Playwright supports --workers=4 out of the box), use saved authentication state to skip login in every test, run only smoke tests on PRs and full E2E on merge to main, and delete redundant tests — if three E2E tests cover the same flow, keep one. Aggressive pruning of E2E suites almost always improves signal without losing confidence.

What is mutation testing and is it worth it?

Mutation testing (Stryker for JS/TS, PIT for Java) modifies your code (changes + to -, deletes return statements) and checks whether your tests fail. If a mutation survives, your test suite doesn't actually verify that behavior. It's the most rigorous way to measure test quality. The cost is high — a full mutation test run takes 10-30 minutes. Run it monthly or on critical modules, not on every CI build.

Start Using GitIntel Free

Open source. No account required. Works on any git repository.