Integration issues often appear only after individually tested components start working together. A payment service may process a request correctly on its own, for example, but still fail when the order system sends the wrong data format or expects a different response.
Integration testing helps you catch these problems by checking how modules, APIs, databases, and external services communicate with each other. It focuses on the points where data moves between components and where failures are more likely to be missed during unit testing.
By the end of this article, you will understand how integration testing works, the main approaches and techniques teams use, how to write useful integration test cases, and the common challenges that can affect test reliability.
What is Integration Testing?
Integration testing checks how well individual modules or components interact after being combined into a larger system. It helps identify issues in data flow, communication, and overall functionality between integrated tools.
Read More: What is System Integration Testing
Integration Testing Example
In a banking application, integration testing involves checking the interaction between the front-end interface, transaction processing service, and the backend database. When a user initiates a fund transfer, the test ensures that the transaction is processed, account balances are updated correctly, and the transaction details are recorded consistently across all integrated components.
Read More: How to test Banking Domain Applications
Another example would be customer relationship management (CRM) software, where integration testing ensures smooth communication between the contact management, email marketing, and analytics modules. When a user adds a new contact, the test verifies that the contact is synced correctly across all systems. It also checks that personalized email campaigns are triggered and analytics data is generated to validate smooth system interaction.
Integration Testing in the Test Pyramid
The test pyramid is a model for organizing automated tests. It promotes having more unit tests at the base, fewer integration tests in the middle, and the fewest end-to-end tests at the top. This helps balance speed, cost, and coverage.
The three layers of the test pyramid include,
- Unit Tests: Unit tests form the base of the test pyramid. They involve testing individual functions or methods in isolation to ensure each part of the code behaves as expected. These tests are fast and easy to run and make up the largest portion of the test suite.
- Integration Tests: Integration tests sit at the middle of the pyramid. These tests help catch issues that don’t appear when components are tested separately.
- End-to-End (E2E) Tests: At the top of the pyramid, E2E tests validate the entire system by simulating real user conditions. These tests are slower and fewer, but critical for validating the overall user experience.
Why is Integration Testing important?
A component can pass every unit test and still fail once it starts exchanging data with another service. Integration testing focuses on these handoff points, where differences in data formats, timing, dependencies, or error handling often create defects.
Here is why it matters:
- Finds interface defects early: Integration tests can expose incorrect request formats, missing fields, unexpected response structures, authentication failures, and broken service contracts before they reach system testing or production.
- Validates data flow between components: It checks whether data is passed, transformed, stored, and retrieved correctly across modules. This is especially important when one transaction updates several systems, such as an order service, payment service, and inventory database.
- Checks dependency behavior: Applications often depend on APIs, databases, message queues, and third-party services. Integration testing verifies how the system behaves when those dependencies return valid data, errors, delays, or incomplete responses.
- Catches failures that unit tests cannot: Unit tests usually isolate components using mocks or stubs. Integration tests exercise real connections between selected components, which helps uncover issues caused by configuration, serialization, database queries, network communication, or incorrect assumptions between teams.
- Protects existing integrations during code changes: A change inside one service can affect other components that rely on its API or data model. Running integration tests in CI helps detect these regressions before the change moves further through the delivery pipeline.
- Reduces debugging effort later: When integrations are tested in smaller groups, it is easier to identify which connection caused the failure. Finding the same issue during end-to-end testing can be harder because many services and dependencies are involved at the same time.
What is the Purpose of Integration Testing
The purpose of integration testing is to verify that connected components behave correctly when they exchange data, call each other, or depend on shared systems.
The focus is not on whether each component works on its own. Instead, you are checking whether the assumptions between those components still hold once they are combined.
Integration testing typically helps you validate:
- Interface contracts: Confirm that requests and responses use the expected fields, formats, status codes, headers, and schemas.
- Data consistency: Check that data remains accurate as it moves between services, databases, queues, or external systems. This includes validating transformations and updates across multiple components.
- Control flow between modules: Verify that one component triggers the correct next action. For example, a successful payment should update the order status and start the fulfillment process.
- Failure handling: Test how connected components respond when a dependency is unavailable, returns an error, times out, or sends unexpected data.
- Configuration and connectivity: Validate connection strings, authentication settings, service endpoints, environment variables, and other integration-specific configuration.
- Shared dependencies: Check whether components behave correctly when they rely on the same database, cache, message broker, file store, or third-party service.
Approaches to Integration Testing
The right integration approach depends on how your application is structured, which components are ready first, and how easily you need to isolate failures.
Some teams integrate everything at once. Others connect modules in stages so they can validate each new interaction before adding more dependencies.
1. Big Bang Integration Testing
In the Big Bang approach, all major components are integrated first and then tested together as one system.
This approach can work for small applications where there are only a few modules and most components are already available at the same time. It also requires less planning around integration order.
The main drawback is fault isolation. If a workflow involving five services fails, you may need to inspect several interfaces before finding the actual source of the problem.
When to use it:
- The application has a small number of components
- Most modules are ready at roughly the same time
- Dependencies between modules are relatively simple
- The cost of debugging combined failures is manageable
For systems with many services or complex dependencies, Big Bang testing can make failures harder to trace.
Read More: Top 15 Integration Testing Tools
2. Incremental Integration Testing
Incremental integration testing adds and tests components in stages rather than connecting the complete system at once.
For example, you might first test the interaction between an order service and inventory service. Once that connection is stable, you can add the payment service and test the larger flow.
This makes it easier to identify which new integration introduced a failure. The trade-off is that the team needs a clear integration sequence and may need temporary components such as stubs or drivers.
Incremental testing is commonly implemented using bottom-up, top-down, or sandwich approaches.
2.1 Bottom-up Integration Testing
Bottom-up integration testing starts with lower-level components and gradually adds the higher layers that depend on them.
You might begin with database access and service-layer components before adding APIs and user-facing modules.
This approach is useful when lower-level services contain important business logic or when those services become available before the presentation layer.
When to use it:
- Backend services are available before higher-level modules
- Core business logic sits in lower layers
- Database and service interactions need early validation
- You want to establish a stable foundation before testing higher-level flows
One limitation is that complete user-facing workflows may not be tested until later because the higher layers are integrated last.
2.2 Top-down Integration Testing
Top-down integration testing starts with higher-level modules and progressively connects the lower-level components they depend on.
For example, you may begin testing an order workflow from the application layer while using stubs for payment or inventory services that are not yet available.
This lets teams validate high-level control flow early. It is particularly useful when the main workflows and orchestration logic carry more risk than the lower-level implementation.
When to use it:
- High-level workflows need early validation
- Lower-level services are still under development
- The application has a clear hierarchical architecture
- You can simulate unavailable dependencies with stubs
The quality of those stubs matters. If they behave differently from the real services, some integration defects may remain hidden until the actual dependency is connected.
Also Read: What is System Integration Testing
2.3 Sandwich Integration Testing
Sandwich integration testing combines top-down and bottom-up testing.
Teams test higher-level and lower-level components in parallel, then connect both sides around the middle layer.
For example, one group may validate the user-facing workflow using stubs while another validates database and service interactions using drivers. The two paths are later connected once the middle components are ready.
When to use it:
- The application has several architectural layers
- Different parts of the system can be tested in parallel
- Both high-level workflows and low-level services carry significant risk
- The project has enough coordination and test support to manage both directions
The main challenge is complexity. Teams may need both stubs and drivers, and failures around the middle layer can still require careful investigation.
The approach you choose should make failures easier to locate rather than simply increasing the number of integration tests. For smaller systems, Big Bang testing may be enough. For larger systems with several dependencies, incremental approaches usually give you better control over where and when integrations are introduced.
Integration Testing Techniques
Integration testing techniques help you decide what to validate when two or more components start working together. The focus should stay on interfaces, data exchange, control flow, and dependency behavior rather than on isolated functions.
Depending on how much you know about the internal implementation, you can use black box, white box, or grey box techniques.
Black Box Testing Techniques
Black box testing techniques validate the behavior of integrated components without requiring knowledge of their internal code.
They are useful when you want to verify whether an integration produces the expected output for a given input.
1. State Transition Testing
State transition testing checks whether an integration behaves correctly when the system moves from one state to another.
For example, in an order workflow, a successful payment may move an order from Pending to Confirmed. Integration tests should also verify what happens when the payment fails, times out, or is retried.
This technique is useful for workflows where one component changes the state another component depends on.
2. Decision Table Testing
Decision tables help test integrations where the result depends on several conditions.
For example, an order service may decide whether to reserve inventory based on payment status, stock availability, delivery region, and order type. A decision table lets you map these combinations and verify that connected services produce the correct result for each case.
This is useful for integrations with complex business rules.
3. Boundary Value Analysis
Boundary value analysis checks how integrated components handle values at or around allowed limits.
Suppose one service accepts up to 100 items in an order while another service supports only 99. Testing values such as 98, 99, 100, and 101 can reveal mismatched validation rules between the two systems.
This technique is useful when components apply limits to fields such as amount, file size, quantity, or request length.
4. Equivalence Partitioning
Equivalence partitioning groups similar inputs so you do not need to test every possible value.
For an API integration, for example, you may divide payloads into valid data, missing required fields, invalid formats, and unsupported values. You can then select representative inputs from each group.
This reduces the number of tests while still covering the main categories of integration behavior.
5. Error Guessing
Error guessing relies on previous defects, system knowledge, and experience with common integration failures.
You might specifically test expired tokens, duplicate messages, null values, malformed payloads, delayed responses, or unexpected status codes because these conditions often cause problems between services.
This technique works best as a supplement to more systematic coverage.
White Box Testing Techniques
White box testing techniques use knowledge of the internal implementation to test how data and execution move across integrated components.
They are more useful when developers or testers have access to the source code and can identify specific paths that need validation.
1. Data Flow Testing
Data flow testing verifies how data is created, passed, modified, and stored across connected modules.
For example, when a customer updates an address, you may trace how that value moves from the API layer to the customer service, database, and shipping service.
The test should confirm that the same value reaches each required component and that no incorrect transformation occurs along the way.
2. Control Flow Testing
Control flow testing checks the sequence of calls and decisions across integrated code paths.
For example, an order may first validate stock, then process payment, then create a shipment. If payment fails, the shipment service should never be called.
This technique helps verify that connected components execute in the correct order.
3. Branch and Decision Coverage
Branch and decision coverage can be useful when integration logic contains conditional behavior.
Suppose an API handler calls one payment provider for domestic transactions and another for international transactions. Integration tests should exercise both paths and verify that the correct downstream service is called.
The goal is not simply to increase a coverage percentage. It is to make sure important integration branches are actually exercised.
Grey Box Testing Techniques
Grey box testing sits between black box and white box testing. You test the system through its external interfaces while using some knowledge of its internal design.
This is often practical for integration testing because testers may know which services, databases, queues, or APIs are involved without working directly with the source code.
For example, when testing a frontend and backend integration, you may submit a request through the UI while also checking the API response, database update, or message generated in the backend.
Grey box testing is especially useful when you need to verify:
- Data stored after an API request
- Events published to a message queue
- Database updates caused by a frontend action
- Cache changes after service calls
- Interactions with known third-party dependencies
The technique you choose depends on what you need to observe. Black box techniques work well for validating external behavior. White box techniques help trace internal data and execution paths. Grey box testing is useful when you need enough internal visibility to verify what happens between those two points.
Black Box vs White Box vs Grey Box Integration Testing
| Aspect | Black Box Integration Testing | White Box Integration Testing | Grey Box Integration Testing |
|---|---|---|---|
| Knowledge of internal code | Not required | Required | Partial knowledge is useful |
| Main focus | Inputs, outputs, and externally visible behavior | Internal data flow, control flow, and code paths across components | External behavior supported by knowledge of internal architecture |
| What you validate | Whether connected components produce the expected result | Whether data and execution move correctly through integrated code | Whether an external action causes the expected internal changes |
| Common techniques | State transition, decision tables, boundary value analysis, equivalence partitioning, error guessing | Data flow, control flow, branch coverage, decision coverage | API validation, database verification, queue or event verification, cache checks |
| Useful for | API contracts, business workflows, validation rules, and service responses | Complex service logic, conditional integrations, and internal processing paths | Frontend-backend flows, event-driven systems, APIs, databases, and distributed applications |
| Example | Verify that a failed payment keeps an order in Pending status | Verify that payment failure prevents the shipment service from being called | Place an order through the UI, then verify the API response and resulting database record |
| Main advantage | Tests integrations from the consumer’s perspective without depending on implementation details | Provides detailed visibility into where an integration fails | Gives testers more diagnostic information without requiring complete source-code knowledge |
| Main limitation | Identifying the exact internal cause of a failure can be difficult | Tests can become closely tied to implementation details | Requires enough architecture knowledge and access to inspect internal components |
You do not need to choose only one technique for an entire test suite. A practical integration testing strategy often combines them. Black box testing can validate service contracts, grey box testing can confirm database or event changes, and white box testing can cover important internal paths where integration logic contains several branches.
Difference Between Integration Testing and System Testing
Integration testing and system testing both validate how software behaves beyond individual units, but they operate at different levels.
Integration testing checks whether selected modules, services, APIs, or databases work correctly together. System testing evaluates the complete application against functional and non-functional requirements.
| Aspect | Integration Testing | System Testing |
|---|---|---|
| Primary focus | Interactions between connected components | Behavior of the complete application |
| Scope | Specific modules, services, interfaces, or data flows | Entire system and its major user workflows |
| Main objective | Find defects at integration points | Verify that the full system meets defined requirements |
| When it is performed | Usually after unit testing and before system testing | Usually after integration testing |
| Typical defects found | API contract mismatches, incorrect data mapping, failed service calls, configuration issues, broken dependencies | Functional defects, workflow failures, performance issues, usability problems, and security issues |
| Test environment | May use selected real components along with mocks, stubs, or test doubles | Usually uses a production-like environment with most or all components available |
| Test data | Often designed around specific interfaces and component interactions | Covers broader business workflows and end-user scenarios |
| Failure investigation | Usually focused on a smaller set of connected components | Can involve several layers and services because the complete system is under test |
| Example | Verify that a payment service correctly updates the order service after a successful transaction | Verify the complete checkout flow from product selection through payment and order confirmation |
Read More: What is System Integration Testing
Best Practices for Integration Testing
Good integration tests should help you find problems at component boundaries without becoming slow, brittle, or difficult to debug. That means choosing the right integration points, controlling dependencies, and making failures easy to trace.
The following practices help keep integration testing useful as the system grows:
- Prioritize high-risk integration points: Start with interfaces where a failure would have a larger impact, such as payment processing, authentication, order fulfillment, data synchronization, or third-party APIs. You do not need the same depth of testing for every connection. A payment gateway deserves more coverage than an internal service that only reads low-risk reference data.
- Test contracts, not just successful responses: Validate the full contract between components. This can include request schemas, response fields, status codes, headers, authentication, data types, and error formats. If one service changes a field from customerId to customer_id, both services may still work independently while the integration fails.
- Cover failure and timeout scenarios: A reliable integration should handle more than the ideal path. Test what happens when a dependency returns an error, responds slowly, becomes unavailable, or sends incomplete data. Also verify whether retries, fallbacks, rollback logic, or user-facing error states behave as expected.
- Use mocks and stubs selectively: Test doubles are useful when a dependency is unavailable, expensive, rate-limited, or difficult to control. However, relying on them everywhere can hide real integration defects. Use mocks to isolate specific behavior, but keep enough tests with real components to validate actual protocols, schemas, authentication, serialization, and configuration.
- Control the test data and system state: Integration tests often touch databases, queues, caches, or shared services. Tests can become unreliable if they depend on data created by previous runs. Set up the required state before each test and clean it up afterward. Where possible, use dedicated test data or isolated environments so one test cannot affect another.
- Verify side effects, not just returned values: A successful API response does not always mean the complete integration worked. If placing an order should update inventory, create a payment record, publish an event, and change the order status, verify the important side effects as well as the response returned to the caller.
- Keep integration tests focused: An integration test should usually cover a specific interaction or a small chain of related components. If one test crosses the UI, six services, three databases, and a third-party API, a failure becomes much harder to diagnose. That scenario may belong in end-to-end testing instead.
- Make failures observable: Logs, correlation IDs, request IDs, traces, and clear assertions can reduce the time spent identifying which component caused a failed test. For distributed systems, use the same correlation identifier across services so you can follow one transaction through the full integration path.
How to Write Integration Test Cases
Integration test cases should clearly define which components interact, what triggers the interaction, what data moves between them, and what result you expect at each integration point.
Follow these steps to write them.
Step 1: Identify the Components Being Integrated
Start by defining the modules, services, APIs, databases, or external systems involved in the test.
For example:
Order Service → Payment Service → Database
Avoid using a broad scope such as “test checkout.” Naming the actual integration points makes the test easier to understand and debug.
Step 2: Define the Test Objective
Write down exactly what the integration test should verify.
For example:
- Verify that a successful payment updates the corresponding order.
- Verify that a failed payment does not change the order to Confirmed.
- Verify that the inventory service receives the correct quantity after an order is placed.
The test objective should focus on the interaction between components rather than testing one component in isolation.
Step 3: Define the Preconditions
List everything that must already be true before the test starts.
This can include:
- Required services are running
- Test user is authenticated
- Product inventory is available
- Database contains the required records
- API credentials are valid
- Order is in the expected initial state
For example, a payment integration test may require an existing order with the status Pending.
Step 4: Prepare the Test Data
Define the exact test data that will pass between the components.
This can include:
- Request payloads
- User IDs
- Order IDs
- Transaction amounts
- Authentication tokens
- Headers
- Database records
Use data that represents both valid and invalid conditions where required.
Step 5: Trigger the Integration
Specify the action that starts the interaction.
Depending on the system, this could be:
- Sending an API request
- Clicking a UI action
- Publishing a message to a queue
- Updating a database record
- Running a scheduled job
- Calling another service
For example, submitting a payment request may trigger communication between the order service and payment service.
Step 6: Verify the Response Between Components
Check whether the receiving component responds as expected.
Validate details such as:
- HTTP status codes
- Response body
- Response schema
- Error messages
- Headers
- Returned identifiers
Do not stop at checking whether the request returned 200 OK. Verify that the returned data is also correct.
Step 7: Verify Data Flow Across the Integration
Check whether important data remains correct as it moves between components.
For example, if an order contains:
- Order ID: ORD1025
- Amount: ₹2,500
- Currency: INR
Verify that the same values reach the payment service and are stored correctly after processing.
This step is especially important when one service converts or maps data before passing it to another.
Step 8: Verify Side Effects
Many integrations change system state beyond the immediate API response.
For example, after a successful payment, you may need to verify that:
- Order status changes to Confirmed
- Payment record is stored
- Inventory is reserved
- Confirmation event is published
- Notification process is triggered
Testing these side effects helps confirm that the full integration completed successfully.
Step 9: Add Failure Scenarios
Write separate test cases for conditions where the integration does not behave normally.
Common scenarios include:
- Service timeout
- Dependency unavailable
- Invalid authentication
- Malformed response
- Missing fields
- Duplicate requests
- Unexpected status codes
- Network failure
Also verify what the system does after the failure. For example, a payment timeout should not leave the order marked as successfully paid.
Step 10: Define the Expected Final State
Specify what the system should look like after the test completes.
For a successful payment flow, the final state may include:
- Order status is Confirmed
- Payment transaction exists
- Correct amount is recorded
- Inventory has been updated
For a failed payment, the expected state may instead be:
- Order remains Pending
- No successful payment record exists
- Inventory is not permanently reserved
Step 11: Clean Up Test Data
Remove or reset any data created during the test when required.
This can include:
- Database records
- Queue messages
- Temporary files
- Test users
- Cache entries
Keeping tests independent prevents one integration test from affecting another.
Example Integration Test Case
| Field | Example |
|---|---|
| Test scenario | Verify successful payment updates an order |
| Components | Order Service, Payment Service, Database |
| Preconditions | Valid order exists with Pending status |
| Test data | Order ID, customer ID, amount, payment details |
| Trigger | Send payment request |
| Expected response | Payment service returns success |
| Data validation | Order ID and payment amount match across services |
| Side effects | Payment record is created |
| Expected final state | Order status changes to Confirmed |
| Cleanup | Remove test order and payment records |
A good integration test case should make the failure point easy to identify. When a test fails, you should be able to tell whether the problem came from the request, response, data transfer, dependency, or resulting system state.
Tools for Integration Testing
Below are the leading tools used for integration testing across various types of systems and interfaces.
1. Postman
Postman is one of the most popular tools for testing APIs. It provides a complete platform for building requests, managing collections, validating responses, and organizing test suites. Teams use it to test REST and SOAP services during integration testing phases.
| Advantages | Limitations |
|---|---|
| User-friendly interface that simplifies API testing | Primarily focused on API testing and may not cover other types of integration testing |
| Supports automated testing and scripting capabilities | Limited support for complex workflows or UI integration tests |
| Allows for collaboration among team members with shared collections and environments | Cannot simulate UI interactions or full end-to-end user flows |
2. Jenkins
Jenkins is an open-source automation server used to implement continuous integration and delivery pipelines. It automates build, test, and deployment workflows and is commonly used in integration testing to run test suites on every code commit.
| Advantages | Limitations |
|---|---|
| Supports numerous plugins for various testing tools and frameworks | Requires configuration and maintenance, which can be complex for beginners |
| Enables automated testing as part of the build process, ensuring immediate feedback | The initial setup may be time-consuming and resource-intensive |
| Highly customizable and scalable for large projects | Lacks built-in support for test case management or reporting dashboards |
3. Selenium
Selenium is a widely used automation framework designed to test web applications across different browsers and platforms. It is often used to write scripts in various programming languages and simulate real user interactions to validate how frontend components integrate with backend services.
| Advantages | Limitations |
|---|---|
| Supports multiple programming languages, including Java, Python, and C# | Primarily focused on UI testing and requires additional tools for API testing or backend integration |
| Can simulate user interactions, making it suitable for end-to-end integration testing | Complex test scripts may require significant maintenance as applications evolve |
| Large community support and extensive documentation | Slower execution speed compared to headless or API-level tests |
4. Apache Camel
Apache Camel is an open-source integration framework used to route and transform data between systems. It is ideal for testing integrations that rely on messaging, data flow, or communication between services using different protocols.
| Advantages | Limitations |
|---|---|
| Supports a wide range of integration patterns and protocols (e.g., HTTP, JMS, FTP) | Requires knowledge of integration patterns and the Camel framework, which can be steep for new users |
| Offers a powerful DSL (Domain-Specific Language) for defining integration routes | Debugging complex routes may be challenging without proper tooling |
| Facilitates testing of integration logic in a real-world context | Not ideal for lightweight or front-end testing |
5. SoapUI
SoapUI is a dedicated testing tool for both SOAP and REST web services. It supports functional, load, and security testing within a single platform. SoapUI is used during integration testing to validate service responses, run complex test scenarios, and ensure service-level compliance.
| Advantages | Limitations |
|---|---|
| Supports both SOAP and REST web services, providing flexibility for testing | The free version has limitations compared to the Pro version, which offers additional features |
| Supports automated functional and regression testing | May have a steep learning curve for users unfamiliar with API testing |
| Built-in tools for performance and security testing | High memory usage can slow down large test suites or long-running test sessions |
Challenges in Integration Testing
Integration tests deal with more moving parts than unit tests. A failure may come from the component under test, another service, test data, configuration, the network, or the environment itself. This makes both test design and debugging more difficult.
Some of the most common challenges include:
- Managing multiple dependencies: An integration may depend on databases, APIs, message queues, authentication services, or third-party systems. If one dependency is unavailable or behaves differently from expected, the test can fail even when the code being tested is correct.
- Maintaining stable test environments: Integration environments need compatible service versions, configuration, credentials, databases, and infrastructure. Differences between local, test, staging, and production environments can create failures that are difficult to reproduce.
- Creating and maintaining test data: Integration tests often require data to exist across several systems at the same time. Creating that state can be difficult, especially when services maintain separate databases or when one test changes data another test expects.
- Isolating the root cause of failures: A failed workflow may cross several services before the error becomes visible. Without useful logs, traces, or correlation IDs, testers may know that the integration failed but not which component caused it.
- Handling asynchronous communication: Systems that use queues, events, webhooks, or background jobs do not always produce an immediate result. Tests need to account for processing delays without relying on fixed waits that make the suite slow or unreliable.
- Controlling third-party integrations: External services can introduce rate limits, downtime, changing responses, authentication requirements, and usage costs. Mocks can reduce this dependency, but they may also hide problems that only appear against the real service.
- Preventing flaky tests: Network latency, shared environments, stale test data, race conditions, and timing differences can cause the same integration test to pass in one run and fail in another. Frequent false failures make it harder for teams to trust the suite.
Read More: How to avoid Flaky Tests : Methods
- Keeping tests useful as interfaces change: API schemas, database structures, events, and service contracts evolve. Integration tests must change with them. Otherwise, teams can end up maintaining tests for interactions that no longer represent the actual system.
Conclusion
Integration testing gives you confidence that components work correctly once they stop operating in isolation. It checks the points where APIs, services, databases, queues, and external dependencies exchange data or trigger actions.
Effective integration testing means more than verifying successful responses. You also need to validate data consistency, side effects, failure handling, timeouts, and the final system state. The right approach depends on your architecture, but tests should stay focused enough that failures are easy to trace.
