System Integration Testing (SIT) verifies that independently developed systems work correctly together: that the e-commerce platform's order actually reaches the warehouse system, that the payment provider's callback updates the right record, that the nightly export lands in the data warehouse with the fields the BI team expects. Unit tests prove each part works; SIT proves the seams between parts work, which is where a disproportionate share of production incidents live.
Where SIT Sits#
The classic sequence: unit tests, then integration tests within one system, then SIT across systems, then system testing of the assembled whole, then UAT with business users. In practice the phases blur, but the distinction that matters is scope:
| Level | Verifies | Example failure it catches |
|---|---|---|
| Integration testing | Modules inside one system | Service A's repository layer mishandles the ORM |
| SIT | Contracts between systems | CRM sends customer_id as a string, billing expects an integer |
| System testing | One assembled system against its requirements | Feature works but violates the spec |
| E2E testing | A full user journey across everything | User completes checkout but no confirmation email |
SIT failures are rarely "it crashed." They are mismatched field formats, timezone assumptions, encoding differences, retries that duplicate records, and callbacks that arrive out of order.
What SIT Actually Tests#
- Data contracts: every field that crosses a boundary has an agreed type, format, and meaning on both sides
- Interface behavior: APIs, file transfers, queues, and webhooks under normal flow
- Failure handling at the seams: what happens when the downstream is slow, down, or returns an error; whether retries are safe
- Sequence and state: operations that must happen in order across systems (create, then notify, then reconcile)
- End-of-flow reconciliation: the count that left system A equals the count that arrived in system B
A Worked Example: Order-to-Fulfillment#
A concrete seam makes the abstraction real. Say checkout hands an order off to a warehouse system:
| Seam | Contract | Happy path | Failure mode tested | Expected behavior |
|---|---|---|---|---|
| Checkout → Warehouse API | Order JSON: order_id (string), sku (string), qty (integer) | Warehouse returns 201 with a fulfillment_id | Warehouse times out after 30s | Checkout retries once, then queues for manual review, no duplicate order created |
| Warehouse → Checkout webhook | Shipped event: order_id, tracking_number, shipped_at (ISO 8601) | Checkout marks the order shipped and emails the customer | Webhook arrives twice (retry from warehouse's side) | Checkout dedupes on order_id, sends one email, not two |
| Checkout → BI export | Nightly batch: order totals, three-decimal currency amounts | Row counts match between checkout's ledger and the warehouse export | Order placed at 23:59 in one timezone, exported at 00:01 in another | Order lands in the correct day's batch, not dropped or duplicated across the boundary |
None of these three failures would surface in either system's own unit tests, since each system's tests only exercise its own side of the contract. SIT is what actually runs both retries, both timezones, and both duplicate-delivery cases against the real second system instead of a mock of it.
Approaches#
Big bang: connect everything, test the whole mesh at once. Simple to start, brutal to debug, because any failure could be any seam. Defensible only when there are two or three systems.
Incremental: verify one interface at a time, stubbing the systems not yet attached, then widen. Slower to set up, far easier to localize failures. This is the default choice.
Contract testing tools (Pact and friends) shift part of SIT left by letting each side verify the agreed contract in CI without standing up the other system, then SIT proper confirms the live integration in a shared environment.
Limitations to Know#
- Environments are the bottleneck. Real SIT needs all systems present at compatible versions with realistic data; building and scheduling that environment is usually harder than writing the tests.
- Third parties limit fidelity. Payment providers and partner APIs offer sandboxes that approximate production behavior; some failure modes only exist in the real thing.
- SIT is a phase gate, not a guarantee. Contracts verified at release time can still break later when the other team deploys; the seams need watching after the testing ends.
Best Practices#
- Write down the data contract per interface before testing it; the document is half the test
- Test the unhappy paths at every seam: timeouts, duplicates, out-of-order delivery
- Use production-shaped test data; integration bugs hide in real-world formats (names with apostrophes, dates near midnight, amounts with three decimals)
- Automate the high-traffic seams so SIT runs per release instead of per quarter
Conclusion#
SIT is the level that catches what no single team's tests can: two correct systems that are wrong together. Incremental integration, explicit contracts, and unhappy-path coverage at every seam are most of the craft.
The UI-facing slice of those seams can be covered without hand-writing the suite: ObserveOne's Autopilot generates Playwright tests from your running app, the layer where integration breakage actually surfaces to users, and self-heals the selectors when the frontend changes, so cross-system regressions get caught by tests that did not cost a sprint to build.