Test strategy and audit

Unit, integration and end-to-end tests: what is the difference?

Unit, integration and end-to-end tests differ not because one is “better”, but mainly in the width of the boundary and the real parts involved in the run. The narrower the test, the faster the feedback tends to be and the easier it is to localise a defect; the wider the test, the more real connections it can confirm, but the more possible causes a failure can have. A precise distinction helps distribute checks without blind spots or expensive repetition of the same combinations at every level.

Why teams use the names differently

“Unit” does not have to mean one method, and “integration” does not have to mean the whole system. One team calls a service test with a real database an integration test; another calls it a component test and reserves integration for communication between two services. Nor are user interface (UI) and end-to-end (E2E) synonyms: a UI can be tested in isolation, and a complete flow can be started through an API.

The Practical Test Pyramid also notes that layer names are not universal and that a consistent definition within a team matters more than arguing for one vocabulary. Ask three concrete questions about every type:

  1. What is the subject under test, and how does the test enter it?
  2. Which collaborators are real and which are replaced?
  3. Which observable outcome does the test confirm?

This description reveals the true scope even when two teams use the same name differently. In the wider taxonomy this is the level dimension; types of software testing can also be classified by objective, method and run timing.

One online shop, five possible boundaries

Consider a realistic example. An online shop has a price calculator that works with item price, VAT, quantity and a voucher. The cart component uses these rules and prepares a request. The order service stores the order in a database through its API, asks the stock service to reserve the quantity, and asks a payment gateway to create a payment. The web UI lets the customer complete checkout.

The requirement says: voucher SUMMER10 reduces eligible items by 10%, the discount is applied before VAT is calculated, and the final amount is rounded according to the agreed rule. After a successful submission, an order must exist with the same amount, stock must reserve the quantity, and the customer must reach payment.

One requirement therefore creates several distinct questions. Does the rule calculate every boundary case? Does the cart use the rule correctly? Does the database adapter store the exact amount? Do the order and stock services understand one another? Can the customer complete the journey in the deployed system? Each question has a natural test boundary.

Unit test: small behaviour under controlled conditions

A unit test verifies a small unit of behaviour without a real network, database, message queue or another slow or non-deterministic external system. A unit may be a function, a class or a small group of closely related objects. Its boundary is not determined by the number of lines of code, but by what the team considers one coherent behaviour.

In our example, the test calls the calculator with a price of €100, an eligible voucher and a particular VAT rate. It checks the exact subtotal, tax and final price. Other cases cover an invalid voucher, zero quantity, the validity boundary and rounding. The clock can be replaced with a controlled source of time so the test does not depend on today’s date.

A unit test should tell us that the rule is wrong, not that the database failed to start. It is therefore usually fast, repeatable and suitable for many combinations. Its limitation is equally important: it does not check JSON mapping, the database type used for the amount, VAT configuration or whether the UI puts the voucher in the right field.

Test observable behaviour through the unit’s public boundary. If a test dictates the order of every internal call, harmless refactoring may break it without changing the result for the user.

Component test: a complete module, not the whole ecosystem

A component test starts a larger unit and accesses it through its public interface. For a front-end cart, it might render the complete component, enter a voucher and check the displayed amount while replacing the order API with a controlled response. For a back-end service, it might start the application over HTTP with its real internal logic but keep stock and payment as simulations.

This level reveals defects in connections inside the component: the form reads the voucher but does not pass its state to the calculator, or a controller maps the request to the domain model incorrectly. It remains narrower than a test of the complete deployed product, so setup can be more targeted and a failure has fewer possible causes.

Define the word component within the project. It can mean a visual element, a package or an independently deployable service. Without a stated boundary, the name alone does not say what assurance the test provides.

Integration test: evidence at a real boundary

An integration test checks that two or more real parts exchange data and behaviour correctly. A narrow integration test might involve the order service and database: after calling the repository, it checks the amount, currency, items and state stored in the real supported database. Another connects a producer to a real message broker to check the topic name, serialisation and acknowledgement.

A wider integration test may run the order service together with a test instance of the stock service. It checks that the reservation request contains the correct identifiers and that an “insufficient stock” response moves the order into the correct state. If a simulation replaces the stock service, the test covers the real HTTP behaviour of the order service, not compatibility with the stock service’s current implementation.

This is why real dependencies must be recorded. A different database type from production may suit some fast checks, but may not reveal differences in types, transactions or SQL. Conversely, starting every remote service for every test makes setup more expensive and increases unrelated failures.

At API boundaries, contract testing complements integration tests. A contract verifies recorded consumer and provider expectations without a shared run of the entire system. It does not replace tests of network configuration, authentication or real side effects.

System and E2E tests: the whole and the complete journey

A system test verifies the assembled product as a whole against a requirement. It can use a public API or UI and does not have to cross into other organisations or external systems. An E2E test follows a complete business or user flow across the relevant boundaries from trigger to final result.

In an online shop, an E2E scenario might create a product and voucher, open checkout, submit the order, complete a test payment and verify the order state and stock reservation. If the payment gateway does not provide a safe sandbox, the team may replace it in the controlled environment. The test still traverses an end-to-end journey within its own system, but it does not confirm the real gateway integration; that needs a separate test under permitted conditions.

E2E provides evidence that the selected journey works as a whole, but a failure may come from the UI, API, data, network, stock service, payment or environment. Do not send every voucher combination through it. One or a few representative journeys confirm the connections, while price boundaries belong lower down. Automated online shop testing discusses practical purchase journeys.

An acceptance test is not another synonym. It says whether a condition for acceptance is met; it can be a unit, component, API or E2E test, depending on where sufficient evidence is produced.

Test doubles, stubs, fakes and mocks

Test double is the umbrella term for a replacement for a production dependency in a test. Library terminology varies, but it helps to understand the role of each substitute:

Substitutes give a test control over errors, time and edge conditions. They also create a risk: a test may prove only that the code works with our idea of a third-party service. The more important the boundary, the more a simulation should be complemented by a contract or a narrow test with the real implementation. Mock primarily at external boundaries, and do not base the entire test on checking internal calls that a user cannot observe.

Comparison without a false hierarchy

Type Typical boundary Real dependencies Strength Main limitation
Unit small rule or object usually no network or database fast combinations, precise diagnosis does not confirm connections
Component module, UI component or service internals real, surroundings often replaced behaviour of the unit through a public boundary the term needs a local definition
Integration one or more technical boundaries selected neighbouring parts real schemas, protocols, databases, configuration more demanding setup and isolation
System assembled product most owned parts real requirements for whole-product behaviour wider set of failure causes
E2E the complete selected journey relevant chain within the declared scope confidence in a critical flow slower runs, data and diagnosis

The table does not rank importance. A unit test cannot replace an integration, and an E2E test cannot replace precise coverage of a rule. Each answers a different question.

How to choose a level for a specific risk

First write the claim that the test must prove. Then choose the lowest boundary at which that claim remains true:

Then remove unnecessary repetition. If unit tests reliably cover twenty price combinations, the E2E test only needs a representative price and correct propagation of the result. Do not, however, remove a boundary check merely because the rule is covered lower down.

The test pyramid is useful here as a direction towards more narrow tests and fewer broad tests, not as a mandatory ratio. A product with substantial proprietary logic will have a different distribution from an integration application built over third-party services. Decide according to the architecture, consequence of failure, feedback speed and maintenance cost.

What to watch for in a real suite

Many mocks can create a fast green suite that cannot see an incorrect schema or configuration. Many broad tests make runs longer and create instability when they share data. A test that passes only in a particular order does not provide reliable evidence; it helps when the scenario prepares its own independent test data and cleans it up in a controlled way.

Beware of unclear reporting too. A name such as “checkout works” does not say whether the calculation, HTTP contract or display failed. Keep the test boundary, input, expectation and diagnostic details visible. When a broad test fails, narrow tests should help isolate the cause, not merely repeat the same scenario with another tool.

Adapt the distribution of runs to the required feedback. Fast unit and component tests can run for every change, selected integrations according to the affected area, and a small critical E2E suite after deployment. Wider regression can run in a later stage; the guide to automated tests in CI/CD summarises the options.

What an appropriate distribution gives you

A layered suite gives the team quick information about an incorrect rule and separate evidence that key boundaries work. It reduces the need to send every combination through an expensive complete journey, without pretending that isolated mocks confirmed the real deployment. A failure is easier to assign to an owner, and an architectural change makes it clear which boundaries need to be reconsidered.

Next step

Select ten critical scenarios and list the individual claims you currently test for each. Add the boundary, real dependencies, test doubles used, run time and typical causes of failure to every claim. Mark claims needlessly repeated in E2E and boundaries that are never verified with real parts. Then move one group of combinations to a narrower level and retain one representative end-to-end scenario as evidence of the complete flow.

Related topics

You might also be interested in

Let's clarify where automation has the greatest expected benefit

We will assess your testing process and suggest what to automate, what to keep manual and what to start with.