Unit, integration and end-to-end testing explained simply, with the testing pyramid, what each type catches and how much of each you need.
In this article
The three main layers of automated software testing are unit tests (one small piece of code checked in isolation), integration tests (several parts checked working together, such as code plus database) and end-to-end tests (a whole user journey checked through the real interface). Most teams aim for many fast unit tests, a moderate number of integration tests and a small number of end-to-end tests, a shape often called the testing pyramid. Around these sit other kinds of testing, from performance and security to the human checks of user acceptance. This guide explains each type, what it catches and how to balance them.
Why testing types matter
No single kind of test finds every problem. Unit tests are fast but cannot see whether two components agree with each other. End-to-end tests see everything but are slow and fragile. Using each type for what it does best gives you good protection at a sensible cost, and a failing test tells you roughly where to look.
Unit testing
A unit test exercises a small, isolated piece of logic: a function, a method or a class. Dependencies such as databases or external services are replaced with simple stand-ins, so the test runs in milliseconds and fails for one reason only.
Example: a function that applies a discount. Tests might check a normal discount, zero quantity, a negative price, and a rounding case.
test('applies 10% discount to orders over 100', () => {
expect(applyDiscount(200)).toBe(180);
});
test('does not discount small orders', () => {
expect(applyDiscount(50)).toBe(50);
});
Catches: logic errors, edge cases, regressions when someone changes a function. Misses: problems in how components connect.
Integration testing
Integration tests check that parts of the system work together: your code with a real (usually test) database, one service calling another, or an API endpoint returning the right data. They are slower than unit tests, and they find a different class of bug.
Example: sending a request to the "create order" endpoint and checking that a row appears in the test database and the response contains the order number.
Catches: mismatched data formats, wrong queries, misconfigured connections, broken contracts between services. Misses: problems that only appear in the full user interface.
End-to-end (E2E) testing
End-to-end tests drive the whole application the way a user would, often through a real or headless browser: log in, add an item to the basket, pay, see the confirmation. They give the closest thing to automatic reassurance that the system works as a whole.
Catches: broken user journeys, front-end and back-end disagreements, configuration issues. Costs: they are slow, and they can be flaky when timing or test data is inconsistent. Keep them for the handful of journeys that matter most to the business, such as sign-up, checkout or submitting a claim.
The testing pyramid
| Layer | Quantity | Speed | Cost to maintain | Typical focus |
|---|---|---|---|---|
| Unit | Many | Very fast | Low | Business rules, calculations, validation |
| Integration | Some | Moderate | Medium | Database, APIs, services working together |
| End-to-end | Few | Slow | High | Critical user journeys |
The pyramid is a guide, not a law. Some applications, such as those that are mostly data plumbing between systems, benefit from more integration tests and fewer unit tests. The principle holds: push checks down to the cheapest layer that can reliably catch the problem.
Other types of testing you should know
Functional and regression testing
Functional testing confirms features behave according to the requirements. Regression testing re-runs earlier tests to make sure new changes have not broken old behaviour. Automating regression tests is a main reason for building a test suite at all.
Performance and load testing
These check how the system behaves under expected and heavy use: response times, concurrent users, large data volumes. Do this before launch if you expect load, not after complaints.
Security testing
This includes automated dependency scanning, code analysis and manual penetration testing. The OWASP Top 10 is a widely used reference list of common web application risks. Security testing should be part of the process, not a last-minute extra.
Usability and accessibility testing
Do real people understand the screens? Can someone using a keyboard or screen reader complete the task? Automated checks help, but they cannot replace watching someone use the software.
User acceptance testing (UAT)
Business users try the system against real scenarios and confirm it is acceptable. It is your chance to confirm that what was built matches how the work is really done. Plan time and people for it, and write acceptance criteria beforehand; our guide to writing a requirements document shows how.
Exploratory testing
A skilled tester explores the software without a script, looking for the odd cases nobody thought of. It finds things automation does not.
Where testing fits in delivery
Tests are most valuable when they run automatically on every change. That is the idea behind a pipeline: build, run unit and integration tests, deploy to a test environment, run a few end-to-end checks. We explain this in what CI/CD is and why it matters. Teams that also write tests alongside the code, or before it (test-driven development), tend to find that design improves as well, though it takes discipline.
A practical testing checklist
- Business rules that affect money, access or compliance have unit tests.
- Every external integration has at least a basic integration test.
- The three to five most important user journeys have end-to-end tests.
- Tests run automatically on every change, and failures block release.
- There is a test environment with realistic but non-sensitive data.
- Performance and security checks happen before launch and after major changes.
- Real users do acceptance testing against written criteria.
Common mistakes
- Relying only on manual testing. It does not scale, and people skip steps on busy days.
- Too many end-to-end tests. Slow, brittle suites get ignored. Keep them focused.
- Testing implementation rather than behaviour. Tests that mirror the code break whenever it is refactored.
- Chasing coverage numbers. A high percentage of meaningless tests gives false comfort.
- Ignoring flaky tests. If a test fails randomly, fix or remove it, or people stop trusting all of them.
- Using production data carelessly. Real personal data in test systems creates privacy and GDPR risk; use anonymised or synthetic data.
- Leaving testing until the end. Late bugs are the most expensive to fix.
Choosing what to test first
If you are starting from very little, you cannot test everything at once. Rank areas of the system by two questions: how bad would it be if this broke, and how often does it change? Money calculations, access control, data imports and anything customer-facing score highly on the first; frequently edited modules score highly on the second.
A sensible order is to add unit tests around the business rules that are both critical and changing, then integration tests for the points where your system meets a database or an external service, then a very small set of end-to-end tests covering sign-in and the main revenue or workflow path. Whenever a bug reaches production, write a test that would have caught it before fixing it. Over time that habit builds a safety net aimed exactly at the places where your system tends to fail, which is far more useful than a large suite written without thought.
Testing as part of the build
If you commission software, ask how it will be tested at each layer, who runs acceptance testing and how failures are reported. Soch Tech's delivery process includes testing and deployment so that what is launched has been checked. To see how that fits into a project, read about our custom software development service.
Share this article
General educational information, not legal or professional advice for your specific situation.
