Unit, Service, UI, and E2E describe different aspects of testing. Unit and Service usually describe scope boundaries; UI describes the interaction point; E2E describes the breadth of a business flow. A UI test is therefore not necessarily a full E2E test, and an end-to-end check can run through APIs without a browser.
This distinction is practical for QA. Before discussing coverage, draw the boundary: which components run for real, which dependencies are replaced, and what outcome is observed? The word "integration" then stops concealing several different types of testing.
Unit: a narrow responsibility
A unit test checks a limited unit of behavior under controlled conditions. This may be a function, class, or small related module. External networks, clocks, and storage are usually replaced with controlled dependencies so the check explains a specific rule.
For example, a delivery calculation receives weight, region, and tariff. This level suits zero weight, range boundaries, rounding, and an invalid region. Passing does not establish that the HTTP handler maps the request's region correctly or that the UI displays the calculated price.
Service: behavior at a service boundary
A service check interacts with an application's public boundary or part of it, often an HTTP API, queue, or RPC interface. Validation, permissions, response codes, serialization, and state changes matter here. Whether the database and neighboring services are real depends on the suite's purpose.
State this explicitly: "API with a real test database and a stubbed payment provider." This tests order persistence well, but does not establish compatibility with the real provider. That needs a separate contract or integration check in an agreed environment.
UI: the user interface
A UI check interacts with presentation: a browser, component, or mobile screen. It can check a form with a supplied API response without running the backend. This is useful for error states, loading, empty data, and interactions among elements.
When the test completely controls the server response, a pass establishes correct handling of that particular response. It says nothing about whether the backend actually returns that format. An agreed data contract and separate contract checks remain necessary.
E2E: a complete flow
End-to-end testing checks interactions among several important system parts from entry to business outcome. For example, creating an order through the UI, processing it in the service, and seeing it in order history. Define where the flow ends beforehand: payment acceptance, event publication, or actual notification delivery.
A test with substituted payment behavior may remain a useful broad check of your application, but state the limitation in its name or documentation. Making absolutely everything real is not always achievable or necessary; describing the exact connection being tested matters more.
Contract checks between levels
A contract test checks the agreement between consumer and provider: fields, types, required properties, and permitted response variants. It helps find incompatibility before a full end-to-end run. Schema validation is one tool, but it does not establish the meaning of every value.
A structurally correct JSON response may contain another user's order or an incorrect amount. Business checks therefore complement the contract. Conversely, a business calculation test will not detect a renamed field that breaks a consumer. These are separate responsibilities and awkward to combine into one coverage metric.
Practical example: changing an address
In a training store, orders may ship only to allowed addresses. Take a user named Anna, her order in draft status, and an address in an unsupported country. Requirements say the system must reject the save, display a message, and preserve the previous order address.
A unit test checks the allowed-country rule. A service test sends an address change request and expects the agreed error code, a reason code in JSON, and an unchanged record. A UI test with a controlled response checks that the message is visible and entered fields remain intact.
One E2E test repeats the rejection through the real interface and service. An allowed address then succeeds through persistence. If the end-to-end test fails while narrow checks pass, QA compares the actual request, response, and display: field mapping or contract handling may be wrong.
Where to prepare data
Setup must not silently remove the behavior under test. If testing checkout, a fixture or test API can create the user. If testing registration, do not create the user beforehand: that removes the relevant behavior from the scenario.
Setup operations should return specific identifiers and ensure independence. A shared order "for all tests" creates order dependence. Separate data and controlled cleanup let a scenario run locally on its own or in parallel without changing its result.
Choosing a level for a defect
After finding a bug, ask at which smallest level its cause reproduces. Incorrect rounding needs a narrow regression check. A missing authorization header needs a boundary check. A button hidden by the keyboard needs a UI check on a suitable device.
Sometimes two tests are needed: a narrow test for the precise cause and a broader test for the connection between parts. However, every defect variant need not move into the browser. Distinct risks and diagnostic value justify checks, rather than a desire to duplicate test counts.
Common mistakes and conclusions
Do not assume every HTTP test integrates real dependencies or every browser test is end-to-end. Judging quality solely by case counts is also risky: a hundred checks of one formula cannot compensate for missing order access control checks.
Establish a project vocabulary for levels with several examples. For each suite, describe boundaries, substituted dependencies, data, and expected signals. This transparency helps QA and developers distribute work and understand what a green run actually means.
Revisit boundaries after architecture changes: a suite's former name may no longer match the components that actually run.