Integration Testing vs. Functional Testing: Key Differences
Integration testing describes a test level; functional testing describes a test type. Learn how they differ, where they overlap, and how to label tests clearly.
Integration testing and functional testing describe different dimensions of a test. Integration testing is a test level focused on interfaces and interactions between integrated components or systems. Functional testing is a test type focused on whether specified functions work correctly. A test can be both: it can exercise an integration boundary while checking required behavior.
This distinction answers the common question, “What is the difference between integration testing and functional testing?” Integration tells you what is connected and where the test sits; functional tells you what behavior the test evaluates.
1. The key difference: test level versus test type
| Dimension | Integration testing | Functional testing |
|---|---|---|
| What the term describes | A test level and its scope | A test type and its objective |
| Main focus | Interfaces and interactions between integrated components or systems | Whether specified functions are performed correctly |
| Typical test basis | Interface contracts, architecture, and interaction requirements | Functional requirements, use cases, or behavior specifications |
| Useful question | Do the connected parts exchange the expected information and handle responses? | Does the system produce the required result for the given conditions? |
| Can it overlap with the other label? | Yes. An integration-level test can check functional behavior. | Yes. Functional testing can happen at different test levels. |
ISTQB defines test levels around the test object, objectives, and development stage. Test types describe activities related to quality characteristics, and can be applied at every test level. The terms therefore are not mutually exclusive categories. ISTQB Foundation Level Syllabus v4.0.1; ASTQB: Test Levels and Test Types.
2. What integration testing covers
Integration testing examines the boundaries where separately built components or systems interact. Its aim is to expose problems in those interactions: for example, incompatible data, incorrect assumptions about responses, or mishandled errors.
Component integration testing
This level focuses on interfaces and interactions between components. A component could be a service, module, or other part of the system, depending on its architecture.
System integration testing
This level focuses on interfaces between the system under test and other systems, including external services. For example, a checkout system communicating with a payment provider is a system-to-system boundary.
The ISTQB syllabus distinguishes component integration testing from system integration testing by the connected parts: components in the former, the system and other systems in the latter. ISTQB syllabus, test levels.
3. What functional testing covers
Functional testing evaluates the functions a component or system should perform against its requirements or other behavior specification. It asks whether the specified inputs, conditions, and actions lead to the expected outputs or outcomes.
Examples of functional questions include:
- Does a valid order proceed to confirmation?
- Is an invalid order rejected as specified?
- Does the system show the required result after a successful action?
ISTQB describes functional testing as evaluating the functions a component or system should perform; its objectives include functional completeness, correctness, and appropriateness. The ISTQB Glossary likewise describes it in relation to satisfying functional requirements. ISTQB syllabus, functional testing; ISTQB Glossary: Functional testing.
4. Can a test be both integration and functional?
Yes. A test can be classified by both its level and its objective. Consider an online checkout that sends a payment request to an external provider:
- Integration scope: the test crosses the boundary between the checkout system and the payment provider.
- Functional objective: it checks that a successful authorization lets the order proceed, or that a declined payment is handled according to requirements.
- Combined description: “Functional system integration test for successful payment authorization.”
This example applies the ISTQB distinction between integration levels and functional objectives; it describes a test design, not a report of testing performed. A test that checks only the request format may focus on interface conformance. A test that also checks the resulting order state has an explicit functional behavior objective.
5. How to classify and describe a test
When a test plan or test report uses a broad label such as “integration test,” readers may not know which boundary or expected behavior is covered. State both dimensions where they matter:
- Name the test object or boundary. Identify the components or systems that interact.
- Name the objective. State the functional requirement or interaction contract being checked.
- State the expected outcome. Include the expected response, state change, or error handling.
- Use both labels when appropriate. For example, “functional component integration test for inventory reservation after order creation.”
A concise test description can follow this pattern: [test type] [test level] for [behavior or interaction] under [condition]. For example: “Functional system integration test for declined payment under an expired-card response.” Use the terms that fit your team’s test plan, while keeping the boundary and expected result explicit.
6. Implementation example: test a checkout integration boundary
The following illustrative Python example shows a functional assertion at an integration boundary. It assumes a local application exposes POST /checkout and a payment-provider test double is configured by the application. The endpoint, payload, and response fields are placeholders for your own contract; they are not a claim about a particular product.
import os
import requests
BASE_URL = os.environ.get("APP_BASE_URL", "http://localhost:8000")
def test_checkout_completes_after_payment_authorization():
response = requests.post(
f"{BASE_URL}/checkout",
json={
"order_id": "order-test-001",
"payment_method": "provider-test-token",
},
timeout=10,
)
assert response.status_code == 200, response.text
body = response.json()
assert body["order_id"] == "order-test-001"
assert body["status"] == "confirmed"
Save as test_checkout_integration.py, install the dependencies with python -m pip install pytest requests, and run pytest -q. This is both an integration test, because it exercises the application boundary and its configured payment integration, and a functional test, because it checks the required order outcome. For a real project, replace the example contract, ensure the provider test double or sandbox is configured, and add a separate case for declined or unavailable payment.
Other implementation choices
- Test double: Use a controlled fake or stub when the goal is repeatable validation of your component’s interaction logic.
- Provider sandbox: Use the external provider’s sandbox when the test must cover more of the real protocol or configuration. Isolate credentials and avoid production side effects.
- Boundary assertion: Assert meaningful request and response behavior, not just that a call occurred.
- Functional assertion: Assert the externally observable result required by the specification, such as order status or error outcome.
7. Troubleshooting integration and functional tests
| Symptom | Likely cause | Practical fix |
|---|---|---|
| Test passes with a mock but fails against the service | The mock does not match the real interface contract or response details. | Compare the test double with the documented contract and add a focused test against the provider sandbox or service boundary. |
| Intermittent timeout | Network or dependency latency, overloaded test environment, or an unbounded wait. | Set explicit timeouts, inspect dependency health, and wait for a stated condition rather than relying on arbitrary long sleeps. |
| Unexpected status or response shape | Environment configuration, API version, or test data differs from the assumption in the test. | Log a redacted response, verify the configured endpoint and version, and align the assertion with the interface contract. |
| Duplicate orders or payments after retry | The test retries a non-idempotent operation without controlling side effects. | Use isolated test data and the provider’s supported idempotency mechanism where applicable; clean up or reset state between runs. |
| Test says “integration” but misses required behavior | The test checks connectivity or message shape only. | Add an assertion for the functional result required by the specification, or describe the test narrowly as an interface check. |
| Test says “functional” but misses a real boundary | The dependency is fully replaced, so the test only evaluates local behavior. | Keep the unit-level test, then add an integration-level case that exercises the intended boundary. |
| Test is hard to diagnose | It combines many boundaries and assertions in one scenario. | Split cases around meaningful outcomes and report which boundary, condition, and expected result failed. |
8. Performance, reliability, and cost considerations
There is no universal rule that integration tests are slower, more reliable, or more valuable than functional tests: these labels describe different dimensions. Test cost depends on what runs, how many dependencies are involved, the environment, and the setup and cleanup required.
- Keep the feedback loop focused. Use narrow tests for individual contracts and a smaller set of end-to-end boundary scenarios for important workflows.
- Control external dependencies. Test doubles can improve repeatability; provider sandboxes can reveal protocol and configuration issues. Choose based on the risk the test must cover.
- Bound waits and retries. Set timeouts and retry only when the operation and test setup make retries safe.
- Isolate state. Use unique records or resettable fixtures to prevent order dependence and duplicate side effects.
- Make failures observable. Record the boundary, correlation identifier where available, condition, and expected outcome; redact credentials and sensitive payment data.
These are implementation practices, not measured performance claims. Neither the terminology nor the cited syllabus supplies a general runtime, defect-rate, or cost figure.
9. ScreenshotNeo for visual checks in a test workflow
If a test needs to inspect a rendered website state, ScreenshotNeo provides a website screenshot API and MCP server for developers. A screenshot is useful evidence for visual output, but it does not establish that an integration contract or functional requirement has passed; keep the relevant assertions in the test itself. See ScreenshotNeo and its API documentation.
Or skip the browser setup
Make one request to capture a page as an image. This runnable cURL example saves a WebP screenshot:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
Equivalent Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
Equivalent Node.js (Node 18 or later, which includes fetch):
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status} ${await res.text()}`);
const bytes = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. An MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month, with no card required.
10. Short FAQ
Is functional testing a test level?
No. In the ISTQB distinction used here, functional testing is a test type; component, system, and other stages are test levels.
Is every integration test functional?
No. Integration identifies the boundary and level. The objective may be functional or another quality concern, depending on what the test evaluates.
Can functional testing happen at unit level?
Yes. Functional testing can be applied at different levels; the important point is to state the behavior and test object clearly.
What is the shortest useful label for an overlapping test?
Include the objective, level, and behavior, such as “functional system integration test for payment decline handling.”


