Skip to content
TestPapas

Integration Testing

What integration testing is, how it differs from unit and end-to-end tests, and how to keep integration tests fast, reliable, and focused on component seams.

Andrew Shassetz· Content Writer at TestPapas
integration testingqa
A futuristic neon purple infographic titled "Where Does Integration Testing Fit In?" comparing "The Test Pyramid" and "The Testing Trophy." The Pyramid shows Unit tests as the largest base, whereas the Trophy highlights Integration as the primary, expanded middle layer. A footer defines testing types from Static to E2E, emphasizing Integration as the "Critical middle layer." Integration Testing

Here's something that doesn't get talked about enough in software development: your tests can all pass, and your software can still be completely broken.

Not because the code is wrong. Because the connections between the code are wrong.

That's the gap integration testing fills. It's the testing process that checks whether your modules, services, databases, and APIs actually connect seamlessly and work together,  not just individually. 

As you read further, you'll learn what integration testing is, the different types of integration testing available, how it fits alongside unit testing and end-to-end testing, and how to build a testing approach that catches the bugs that slip through everything else.

What Is Integration Testing?

Integration testing is a software testing method that validates how software components interact. Rather than checking a single function or module in isolation, an integration test looks at what happens when those parts connect,  when your code talks to a database, calls an external API, or sends a message to a queue.

The meaning of "integration" varies based on your system architecture. Integration testing in a monolithic system verifies that internal layers communicate correctly, such as the service layer communicating with the repository layer, which maps correctly to the database schema. 

While in a microservices system, integration testing involves identifying service boundaries, HTTP contracts, event data formats, and a set of distributed components, where a single integration point can lead to issues with all the components that follow it.

If an integration test fails, it gives specific information. Not "the function is not working", but "something is misconfigured", "data contract has changed", "wiring is incorrect", or "a dependent library behaves differently from what our code expects". Issues such as these can most times slip through the cracks during unit testing and surface in Production environments.

Integration testing is a software testing discipline that falls between unit testing and end-to-end testing in scope. It is often the least well-maintained area of testing, leading to costly mistakes for many teams.

Integration Testing vs Unit Testing vs End-to-End Testing

Testing type

Scope

What it tests

Speed

Strengths

Tradeoffs

Best used for (ROI)

Unit testing

Very narrow (single function/module)

Individual modules in isolation, with dependencies mocked or stubbed

Very fast (often milliseconds)

Great for logic-heavy code and quick feedback

Tests assumptions about dependencies, not the dependencies themselves

Pure logic, calculations, transformations, validation

Integration testing

Medium (specific interaction points)

How real components connect using real wiring, real database calls, and real HTTP clients

Medium

More realistic than unit tests, easier to debug than E2E because the failure surface is smaller

Does not cover full user flows

Boundaries like database access, external API calls, message queues, and auth flows

End-to-end testing (E2E)

Broad (whole system)

Complete user flow from start to finish, often through a UI

Slow

Validates critical journeys end-to-end

Slow, brittle, hard to debug. Root cause can take longer to find than the bug takes to fix

Critical user journeys only

Where Integration Testing Fits in the Testing Process

The test pyramid is a simple but durable model for thinking about your testing approach:

  • At the base: lots of fast, cheap unit tests.

  • In the middle: integration tests.

  • At the top: a small number of slow, expensive E2E tests.

Some teams use the "testing trophy" model instead, which puts integration testing at the center,  the dominant layer of your test suite. Whether you follow the pyramid or the trophy, the point is the same: integration tests are the critical middle layer, and most teams don't invest enough in them.

The most common mistake is letting E2E tests creep downward. Every E2E test you add slows your CI pipeline. A feedback loop that takes 40 minutes kills developer productivity and encourages people to skip tests entirely. Too few integration tests, on the other hand, creates blind spots that only reveal themselves after deployment, often in ways that are expensive to recover from.

The goal of integration testing is to fill that gap,  catching integration issues early, fast enough that developers actually run the suite, with enough precision that failures are easy to diagnose.

Benefits of Integration Testing

There's a whole class of bugs that automated testing at the unit level simply cannot catch. Integration testing is specifically built for them, and the benefits of integration testing compound over time.

Take serialization errors. A unit test can confirm that your createOrder function returns the right object. But does that object serialize correctly when it hits the HTTP layer? Does the JSON your API sends match what the downstream service actually expects? Those failures live in the wiring,  completely invisible to unit tests.

The same is true for configuration issues. An ORM mapping that looks right in your model can silently generate wrong SQL when it runs against a real schema. An auth token your unit test mocks out might use the wrong header name in the actual HTTP client. A database migration might conflict with existing data in ways that only appear during real query execution.

Beyond catching bugs, integration testing offers faster feedback than a full E2E suite. If your integration tests pass, you know the components are connecting correctly. You haven't validated every user journey, but you've verified the critical integration points that matter most.

The long-term benefit most teams notice is fewer production incidents caused by "glue code" failures,  the bugs that live between components rather than inside them. Those bugs are expensive and almost entirely preventable with the right integration testing approach.

If you're not sure where your current testing process has gaps, TestPapas offers professional QA testing services that cover integration testing across a wide range of stacks and architectures.

Types of Integration Testing

There's no single correct way to run integration tests. The right approach depends on your architecture, your team's workflow, and what you're trying to validate. Here are the main types of integration testing you'll encounter.

Incremental Testing vs Big Bang Testing

Big bang integration testing integrates all components at once and tests the whole system together in a single pass. The appeal is simplicity,  no phased approach, no coordination required. But when big bang testing fails (and it usually does), you have no idea where to start. Every component is already integrated, and the failure could be anywhere in the system.

Incremental testing takes a disciplined approach. We integrate and test a small number of elements at a time, ensuring that they can be used together; we then add the next component. The "failure surface" remains small. If things fail, we know it is due to what we have recently implemented, which makes debugging easier. Incremental testing is usually the appropriate default for most teams.

Top-Down Testing, Bottom-Up Integration Testing, and Sandwich Testing

Top-down testing starts with high-level components, like the UI or API, and works downward. Lower-level components are replaced with stubs until they’re available. This lets teams verify overall system behavior early, but requires maintaining many stubs.

Bottom-up testing is the opposite. It begins with low-level components such as databases or core services and moves upward. Higher-level components are replaced with test drivers. This approach helps catch foundational issues early, especially when lower layers carry high business risks.

Sandwich testing combines both approaches, integrating mid-level components while leveraging top-down and bottom-up methods. It’s more complex but useful for large projects where multiple teams integrate different system layers.

Service and API Integration Tests

In systems built around multiple services, API integration testing deserves its own category. These tests validate service boundaries: the request format, response structure, error handling, authentication behavior, and how your HTTP client handles different response types. The goal here isn't to test the downstream service in isolation; it's to test your side of the integration, with real app wiring and controlled dependencies replacing unpredictable live network calls.

What to Cover in Integration Tests

Not everything needs an integration test. Trying to cover everything will bloat your suite and slow your pipeline. Focus your integration testing effort on the parts of a software application where integration failures are most common and most costly.

Database and Persistence Layer

The database layer is where integration testing delivers the most consistent value. Your ORM mappings can lie to you in unit tests; only real queries against a real schema reveal whether your model actually reflects your database structure. Test your migrations by running them against a fresh database. 

Test your transactions to confirm they commit and roll back correctly under the right conditions. Test your repository methods with actual query execution, not mocked return values. Any complex ORM relationship deserves an integration test.

External APIs and HTTP Clients

Your HTTP client configuration is full of potential failure points. Write integration tests that verify your client sends the correct headers, constructs payloads properly, handles authentication correctly, and maps error responses into the right application-level exceptions. 

The key here is using a testing tool that stubs the actual HTTP layer, not mocking your client class, so you're validating your real client configuration rather than your assumptions about it. API testing at this level catches issues that almost nothing else will.

Messaging and Events

If you're using a message queue or event stream, integration testing is non-negotiable. Verify that your producer sends messages in the expected schema. Confirm your consumer handles them correctly, including edge cases like malformed payloads or duplicate delivery. 

Test idempotency: What happens when the same message comes twice? Schema compatibility between producers and consumers is one of the most vulnerable points for integrating into distributed systems; automated integration tests provide a good way to identify faults before they lead to disruptions in live production.

How to Write Integration Tests That Stay Fast and Reliable

Integration tests have a reputation for being slow and flaky. That reputation usually comes from poorly structured tests, not from the approach itself. Here's how to write integration tests that are worth maintaining.

Use Real Dependencies Where They Add Confidence

The value of integration testing comes from using real implementations of the components you care about. A real database catches real query failures. Real app wiring catches real configuration mistakes. Use real dependencies for the parts of the system you own and control.

The boundary to draw is around anything external. Real third-party network calls inside automated integration tests introduce latency, rate limits, and failure modes that have nothing to do with your code. Automated testing that relies on live external services will eventually fail for reasons outside your control,  and that erodes trust in your entire suite.

Fake or Stub the Boundaries You Don't Control

Payments, email delivery, SMS, third-party analytics, these should almost always be stubbed. Use stubs that accurately model the response shape of the real service rather than simplified hand-rolled mocks that drift from reality over time. 

Stable, maintained stubs are far more valuable than fragile live calls. Popular integration testing tools like WireMock or recorded HTTP fixtures work well here, as long as you keep those stubs updated when the real service changes.

Control the Sources of Flakiness

Flaky integration tests are one of the most damaging things that can happen to a testing culture. Developers stop trusting the suite, start ignoring failures, and eventually stop running it altogether.

The main sources of flakiness are time, randomness, async behavior, and shared state. For time-dependent code, inject a fake clock or use a fixed timestamp. For random data, seed your ID generators. For async behavior, use explicit waits with realistic timeouts rather than arbitrary sleep calls.

The shared state between tests is the biggest culprit of all. Each test should start from a clean, known state. Reset your database between tests,  whether that's a rollback, a truncate, or a full recreate, depending on your stack, but some form of automated reset is essential. Never share mutable fixtures between tests.

Integration Testing Tools and Framework Setup

Getting your environment right is half the work in integration testing. The modern standard is containers for dependencies. Tools like Docker Compose or Testcontainers let you spin up a real Postgres, Redis, or Kafka instance per test run, scoped to your suite, isolated from shared environments, and reset automatically between runs.

Shared staging databases are the enemy of reliable integration tests. When multiple developers or test runs share the same database, you get interference, timing issues, and failures that are nearly impossible to reproduce. Containers solve this by giving every run its own clean stack.

On the question of integration testing tools and what they're good for: mocks verify behavior (was this method called?), stubs return controlled responses (what does this dependency return?), and simulators model the full behavior of an external system. For most integration testing scenarios, stubs and simulators are more useful than mocks, because you care about the result of the interaction, not just whether a call was made.

Test data strategy is the other piece. Fixtures, predefined static datasets, are simple but become brittle as your schema evolves. Factories,  programmatic builders that create test data on demand,  are more flexible and scale better. The practical default is a combination: a minimal fixture for schema-level baseline setup, with factories for test-specific data. Pair this with an automated reset strategy, and you have the foundation of a maintainable integration testing framework.

Common Pitfalls (and How to Avoid Them)

Flaky Tests from Shared State and Live Networks. Integration tests can quickly lose your team’s trust if they fail unpredictably. Most flakiness comes from shared database state, if one test leaves a row behind, the next test can fail with a constraint error. Another common issue is relying on real external networks during tests. The solution is isolation: use separate containers for each test run, reset the database state before each test, and use stubs for anything outside your control.

Duplicating End-to-End (E2E) Coverage. Sometimes integration tests try to cover entire user flows. This often duplicates your E2E tests but is slower to write and harder to maintain. Focus integration tests on seams, the points where components interact. If a test needs five services and ten setup steps, it belongs in the E2E suite, not integration.

Slow Setup and Large Test Fixtures. Integration tests are naturally slower than unit tests, but they shouldn’t feel like a burden. Loading thousands of rows or initializing containers that take minutes will make developers skip tests. Keep fixtures small, use fast container startup strategies (like pre-built images or caching), and reset only what’s necessary instead of rebuilding everything each time.

Conclusion

Integration testing is where you catch the bugs that slip past unit tests. Unit tests are great, but they only check code in isolation. Integration tests show whether your components actually work together, with real databases, HTTP clients, and message queues.

The goal here is to keep it focused: test the critical points, use real dependencies when it matters, stub what you don’t control, and always keep your state clean. When done right, integration testing is one of the most valuable parts of your workflow, not a slow chore. If you want help building or improving your setup, the TestPapas team can step in and make it work at scale.

Frequently asked questions

Quick answers to the questions readers ask most often.

  • Integration testing checks how specific software components interact with each other. System testing validates the whole system against its functional and non-functional requirements as a complete unit. Integration testing is narrower in scope and runs earlier in the development cycle; system testing comes after integration is complete and looks at the entire software system end-to-end.
  • They overlap but aren't identical. API testing focuses on validating endpoint behavior, inputs, outputs, and status codes. An integration test involving an API goes further: it verifies that your application's real HTTP client is correctly configured, handles responses properly, and maps errors into the right application exceptions. API integration is a subset of broader integration testing.
  • In most cases, yes. A real database in a container catches migration issues, query failures, ORM mapping bugs, and transaction behavior that mocked databases completely miss. The extra setup complexity is worth the confidence. Use a tool like Testcontainers to keep it isolated, reproducible, and fast to spin up without touching a shared environment.
  • Enough to cover your critical integration points, database interactions, external API calls, message queue behavior, and authentication flows. There's no universal number. A useful rule of thumb: any integration point that has caused a production incident deserves a test. Focus on quality and targeting over volume.
  • Eliminate shared state first, make sure every test resets the database, and doesn't depend on data left behind by previous tests. Stub external networks so your tests don't fail because a third-party API is slow or unavailable. Handle time-dependent code with fake clocks. Most flakiness in integration test suites traces back to these three root causes.
  • Contract testing verifies that two services agree on the shape of their interaction, what the producer sends, and what the consumer expects. It's complementary to integration testing. Where integration testing validates that your code correctly executes an integration, contract testing ensures both sides of that integration agree on the terms before execution. Tools like Pact are widely used for this. The two approaches work best together, especially in microservices architectures.

Written by

Andrew Shassetz

Content Writer at TestPapas

A content writer with 7+ years of experience in B2B technology, SaaS, and fintech. He covers software testing, QA automation, web and mobile app testing, and payment localization for fintech and iGaming audiences. Outside of work, an avid wrestling fan and enthusiastic home cook.

Website Testing

Find bugs, broken flows, and performance issues on real devices across browsers and OS — detailed reports with screen recordings in 24–72 hours.

Get started

Ready to catch the bugs that matter?

TestPapas deploys real testers in the markets you care about — iGaming, fintech, and beyond.