Integration Testing

Integration testing is the test level that focuses on the interactions between components or between systems. Each part may already pass its own tests; integration testing asks whether they work together: whether the data one part sends is what the other expects, in the format, units, order and timing it expects. Example: a checkout sends "amount": 150.00, meaning 150 riyals, to a payment gateway that reads amounts in halalas, and the customer is charged 1.50 SAR. The defect is in neither component but in the interface between them.

The ISTQB Foundation syllabus v4.0 distinguishes two integration levels, component integration and system integration, described below. Both build on component testing: parts proven in isolation are now combined.

Why it matters

An interface is where two people’s assumptions meet. When their readings of the agreement differ (riyals or halalas, local time or UTC, a missing field or a null), every component test passes and the defect appears only when the parts run together. A stub rarely reveals it, because it is usually written with the same assumption as the code that calls it.

For the product, integration defects mean wrong charges, lost orders and duplicate messages, often found in production because an external system was only ever simulated. For the tester, this is where reading both sides of an interface pays off.

Component integration and system integration

Integration tests concentrate on the interface: the calls, messages and data passed between parts, and what happens when the other side fails, not the logic inside each component. The test basis is whatever describes the interface: an API specification such as an OpenAPI document, message formats, sequence diagrams.

  • Component integration testing: the interfaces between the components of one system, such as the order service calling the inventory component. Usually automated by developers in the continuous integration pipeline.
  • System integration testing: the interfaces with other systems and external services, such as a payment gateway or an SMS provider. Usually led by testers; it needs an environment close to production, a sandbox from the other party, and coordination with a team you do not control.

Big-bang or incremental: top-down, bottom-up, stubs and drivers

In big-bang integration, all components are combined at once and tested together. No stand-ins are needed, but testing starts only when every part is ready, and when a test fails the cause can be in any interface. That is acceptable for a very small system; in a larger one, finding the failing interface can take longer than the testing.

Incremental integration adds one component, or a few, at a time and tests each new interface before the next, so a failure points first to the interface just added. Stand-ins, which you met in component testing, replace the missing parts. A stub replaces a called component and returns prepared answers. A driver replaces a calling component: a test script or an API client that calls the component under test as the missing caller would.

  • Top-down: start at the top (an API endpoint or the user interface) with stubs for the components below, and replace them one by one. The main flow is visible early; calculations and data access are tested last, and stubs that must return realistic data can grow complex.
  • Bottom-up: start with the lowest components (calculations, data access, clients for external services), called by drivers, and work upwards. Foundations are tested early and failures are easy to locate; the complete flow is visible only at the end.
  • Many teams combine both and meet in the middle, which is called sandwich or hybrid integration.
Big-bang combines all checkout components at once, so a failing test can point to any interface; top-down replaces stubs from the top, bottom-up replaces drivers from the bottom, one interface at a time. Conceptual model

Big-bang

  1. 1Every component built and component-tested
  2. 2All combined in one stepFrom checkout-api down to payment-client
  3. 3A test failsThe cause can be in any interfaceFail
Stand-ins needed:
None
Testing starts:
When every part is ready
A failure points to:
Any of the interfaces
Seen early · tested late:
Nothing early; everything at the end

Top-downincremental

  1. 1Start with checkout-apiA stub replaces order-service
  2. 2Real order-service replaces its stubStubs for the components below it
  3. 3Real lower components replace the last stubs
Stand-ins needed:
Stubs, in place of called components
Testing starts:
As soon as the top layer exists
A failure points to:
First the interface just added
Seen early · tested late:
Early: the main flow · Late: calculations and data access

Bottom-upincremental

  1. 1Drivers call the lowest componentsprice-calculator, inventory, payment-client
  2. 2A driver calls order-serviceNow with the real lower components
  3. 3checkout-api replaces the driver
Stand-ins needed:
Drivers, in place of the callers
Testing starts:
As soon as the lowest components exist
A failure points to:
First the interface just added
Seen early · tested late:
Early: the foundations · Late: the complete flow

Try it: Mock API Response Generator Try it: API Request Builder

Interface defects: what to look for

Interface defects come from two sides that disagree, or from one side that cannot cope when the other fails. A practical first check is to compare each side’s actual requests and responses with the agreed specification, such as the OpenAPI document: a difference shows which side departs from the agreement. Then ask these questions of every interface:

  • Format and encoding: is 01/02/2026 the 1st of February or the 2nd of January? Do Arabic names arrive intact?
  • Units and meaning: riyals or halalas, seconds or milliseconds, local time or UTC, price with or without VAT.
  • Structure: a missing, renamed or extra field; null versus an absent field; enum values that differ (CANCELLED, CANCELED); different length limits on each side.
  • Order and timing: calls in the wrong order, a callback that arrives before its record is saved, a timeout shorter than the other side’s normal response time.
  • Failure handling: when the other side is down, slow, returns a 500 or an unexpected error body, or answers twice, does the caller retry safely, avoid charging twice and keep the data consistent?

Try it: API Contract Validator Try it: Response Contract Comparator

Worked example: integrating a checkout

A shop’s checkout has five components and calls one external system, as in the plan below. The team integrates bottom-up: the checkout API is written last, and the gateway’s sandbox account is still being arranged, so a stub stands in for the gateway in steps 1 to 3.

The stub was written from the payment client’s own code, so it accepted any amount, and every test in steps 1 to 3 passed. Step 4 runs the same tests against the real sandbox:

  • A 150.00 SAR order creates a 1.50 SAR charge: the gateway expects whole halalas, and in JSON 150.00 is simply the number 150. The defect report names the interface (payment-client to gateway), the request and the response.
  • Step 4 also covers what only the real system shows: its actual decline codes, its behaviour on timeouts and retries (one charge only), and when its callback arrives.
checkout-api               POST /checkout                          (top)
└── order-service          placeOrder(cart)
    ├── price-calculator   total(cart): SAR, 2 decimals
    ├── inventory          reserve(sku, qty), release(sku, qty)
    └── payment-client     charge(amount, currency)
            └──► payment gateway                        (external system)

Integration order: bottom-up
  1  drivers → price-calculator, inventory, payment-client   gateway: stub
  2  driver → order-service + the components from step 1    gateway: stub
  3  checkout-api replaces the driver                       gateway: stub
  4  the same tests, against the gateway sandbox            system integration

Try it: API Status Code Simulator Try it: Mock Error Response Generator

Common mistakes

  • Re-testing each component’s business rules instead of the interface: the data passed, the order of calls, the handling of failures.
  • Trusting a stub built from the caller’s own assumptions instead of the agreed specification, and never testing against the real system.
  • Integrating a large system big-bang, then spending days finding which interface failed.
  • Testing only the happy path: never the timeout, the rejected request, the error body or the duplicate callback.
  • Requesting external sandbox access too late, which squeezes system integration into the last days before release.
  • Assuming "integration test" means the same to everyone. Ask which parts are real and which are replaced.

The QA perspective

A tester’s integration work starts when interfaces are designed. Read the API specification and sequence diagrams during refinement, and ask both sides the same questions: units and formats, required fields, error codes, timeouts, retries. A disagreement settled then never becomes a defect.

Developers usually automate component integration tests; review which interfaces and error paths they cover. Testers usually lead system integration: arrange sandbox access and test data with the external party early, and learn where the sandbox differs from production. When an integration test fails, the report names the interface, includes the exact request and response, and states which side departs from the specification.

Practice exercise

Plan the integration testing of an appointment reminder

Your task

A clinic app sends an SMS reminder 24 hours before each appointment; its parts are below. The booking API is still being written, and the SMS provider’s sandbox will be ready in two weeks. Plan the integration testing.

Material
booking-api                  POST /appointments                     (top)
└── appointment-service      book(patient, slot), cancel(id), sendDueReminders(now)
    ├── schedule-repository  save(appointment), findDue(now)
    └── sms-client           send(phone, text, sendAt)
            └──► SMS provider  POST /v1/messages           (external system)

Clinic data: phone numbers as typed by staff (e.g. 05XXXXXXXX),
             appointment times in Riyadh time (UTC+3)

send(phone, text, sendAt)
  phone    international format (E.164), e.g. +9665XXXXXXXX
  text     reminder text, Arabic or English
  sendAt   ISO 8601 date-time, UTC

SMS provider responses
  202 {"id": "msg-123", "status": "queued"}
  400 invalid number    429 too many requests    503 unavailable
Steps
  1. Mark each interface as component or system integration.
  2. Choose an integration order and list the stubs and drivers it needs.
  3. Write five integration test conditions for the reminder path, each naming the interface defect it targets.
  4. List what must still be checked against the real provider.
Show the model answer
Model answer

Component integration: booking-api → appointment-service, appointment-service → schedule-repository, appointment-service → sms-client. System integration: sms-client → SMS provider.

Bottom-up, because the top layer is not ready: integrate schedule-repository and sms-client, then appointment-service. A driver (a test script or an API client) calls book and cancel until booking-api replaces it, and calls sendDueReminders with a chosen now, which makes the time conditions repeatable. A stub plays the SMS provider and returns 202, 400, 429, 503 and a slow answer.

Conditions: (1) an appointment at 10:00 Riyadh time gives sendAt 07:00 UTC the day before (time zone); (2) 05XXXXXXXX is sent as +9665XXXXXXXX, never prefixed twice (format); (3) an Arabic reminder arrives readable (encoding); (4) a 503 or a timeout keeps the appointment and retries later, sending one SMS only (failure handling); (5) cancelling before sendAt means no reminder (order and timing).

Against the real provider: that its responses match what the stub returned (status codes and body fields), that it reads sendAt as UTC, the sender name, the actual rate limit, how message length is counted for Arabic text, and whether the sandbox returns errors as production does.

Frequently asked questions

Is integration testing the same as end-to-end testing?

No. Integration testing targets specific interfaces: two components, or your system and another one. An end-to-end test follows a complete user journey through the whole system and crosses many interfaces at once. It can reveal integration defects, but it is slower and makes the failing interface harder to find. System and end-to-end testing come in the next lessons.

Who does integration testing, developers or testers?

Usually developers automate component integration tests and testers lead system integration testing, often together with the external party. Teams differ, so agree on it explicitly; either way, a tester reviews which interfaces are covered and whether error paths are tested.

Developers call a test with a real database an integration test. Is that correct?

Many teams use the term that way: any automated test that goes beyond one unit, such as a service with its real database or a real HTTP call. It fits the broad definition, interactions between parts. When someone says "integration test", ask which parts are real and which are replaced; that tells you what the test can and cannot find.

How does continuous integration relate to integration testing?

Continuous integration is a development practice: developers merge small changes into the main line often, and each merge is built and tested automatically. It keeps every integration step small, and integration tests usually run in that pipeline. It does not replace system integration testing with external systems, which needs its own environment.

References

  • ISTQB Certified Tester Foundation Level (CTFL) v4.0ISTQB · 2023The foundation syllabus of software testing: test levels and types, test design techniques, test management and tools.
  • ISTQB GlossaryISTQBThe standard definitions of testing terms — test case, regression testing, severity, priority, boundary value analysis and the rest.
  • The Practical Test Pyramid (Ham Vocke)martinfowler.com · 2018A widely cited explanation of how to balance unit, integration, contract and UI tests in an automated test portfolio.
  • Continuous Integration (Martin Fowler)martinfowler.com · 2024The widely referenced description of continuous integration: integrate often, keep the build self-testing and fast, and fix a broken build immediately.

Practise with Mutqan tools

Generate complete mock API responses (status, headers, JSON body) for success, list, detail, error and empty cases.

Mocking

Get a public endpoint that returns any HTTP status code, with optional delay and custom JSON body, to test client error handling.

API Testing

Generate structured error responses (validation, auth, not found, rate limit, server error) in popular formats.

Mocking

Validate that an API request/response pair conforms to its OpenAPI contract: path, method, parameters, body and response schema.

Validation

Compare an actual API response against an expected contract (example or schema) and report breaking vs non-breaking differences.

Validation

Compose and send HTTP requests (method, URL, headers, auth, body) and inspect the response — a lightweight Postman in your browser.

API Testing

Go deeper

Your progress on this topic

Part of the QA Roadmap