What Is Integration Testing in Software Dev?

Summarize this article with:
A module works perfectly on its own, then breaks the moment it connects to another. That scenario plays out in software development projects every single day. Understanding what is integration testing and how it fits into the testing lifecycle helps teams catch these failures before users do.
This article covers how integration testing works, the approaches teams use to structure it, the tools and frameworks behind it, and the common problems that trip people up. You will also see how integration tests fit inside CI/CD pipelines and what best practices actually look like in production codebases.
What Is Integration Testing

Integration testing is a level of software testing where individual modules or components are combined and tested as a group. The goal is to verify that these pieces work together correctly when connected.
Think of it this way. Unit testing checks that each piece of code functions on its own. Integration testing checks what happens when those pieces start talking to each other.
A JetBrains survey of over 26,000 developers found that 47% use integration testing in their projects, making it the second most common testing type after unit testing. That number tells you something about how teams actually prioritize their testing efforts.
Integration testing sits between unit tests and system-level tests in the software testing lifecycle. Unit tests confirm individual functions work. Integration tests confirm those functions play nice together. System tests confirm the whole application behaves correctly end to end.
The bugs caught at this level are specific: data format mismatches between modules, broken API integrations, failed database connections, timing issues in message queues, and authentication handshake failures.
Both developers and QA engineers perform integration tests, though the split depends on team structure. In organizations that follow DevOps practices, developers usually own these tests directly.
Why Integration Testing Matters

A module can pass every unit test and still break the moment it connects to another module. Integration testing exists to catch exactly those failures.
The cost of late detection
IBM’s Systems Sciences Institute found that fixing a bug after product release costs 4 to 5 times more than catching it during design, and up to 100 times more during maintenance. Integration testing catches interface-level bugs before they compound.
The Consortium for Information and Software Quality (CISQ) estimates that poor software quality costs the US economy $2.41 trillion annually. A significant chunk of that comes from defects slipping past inadequate test coverage.
What unit tests miss
Unit tests verify isolated behavior. They use mocks and stubs to simulate dependencies, which means they never actually test the real connections between components.
Here is what falls through the cracks without integration testing:
- API contract violations where one service sends data in a format another doesn’t expect
- Database interaction failures like incorrect query results or connection pool exhaustion
- Authentication handoff problems between frontend and backend services
- Race conditions and timing bugs in asynchronous message processing
Real-world consequences
The July 2024 CrowdStrike incident is the clearest recent example of what happens when testing gaps exist between components. A faulty content update passed CrowdStrike’s validation system due to an undetected bug in their QA logic, then deployed to all customers simultaneously.
The result: 8.5 million Windows systems crashed. Fortune 500 companies absorbed an estimated $5.4 billion in direct losses, according to Parametrix Insurance. Forrester’s analysis called it a quality assurance failure, not a cyberattack.
A Gartner Peer Community survey found that 60% of organizations automate software testing primarily to improve product quality, with 58% driven by the need for faster deployment speed. Integration testing is central to both goals.
Integration Testing vs. Unit Testing vs. System Testing

These three levels of testing get confused constantly. They are not interchangeable, and each catches a different category of defect.
| Testing Level | Scope | What It Catches | Speed |
|---|---|---|---|
| Unit Testing | Single function or method | Logic errors in isolated code | Fast (milliseconds) |
| Integration Testing | Combined modules and interfaces | Interface mismatches, data flow bugs | Moderate (seconds to minutes) |
| System Testing | Full application end to end | Workflow failures, environment issues | Slow (minutes to hours) |
How scope changes everything
Unit tests check a single function. They mock everything external. If your payment service has a calculateTotal() method, the unit test confirms the math works with fake data.
Integration tests connect that payment service to the actual database or a real API endpoint. Now you see whether the calculated total actually gets stored correctly and whether the RESTful API returns the expected response.
System tests run the complete application. A user logs in, adds items, checks out, and receives a confirmation. Every service, every database, every queue is live.
The mocking tradeoff
Mocking in unit tests is heavy by design. You fake everything outside the function under test. That is the whole point.
Integration tests use mocking selectively. You might mock a third-party payment gateway but test the real connection between your order service and your inventory database.
System tests use minimal mocking, if any. The goal is to mirror the production environment as closely as possible.
A concrete example
Say you have a user registration feature. The unit test confirms the email validation regex works. The integration test confirms the registration endpoint writes to the database and triggers a welcome email through the message queue. The system test confirms a user can actually register, verify their email, and log in successfully.
Same feature. Three different levels. Three different bug categories caught.
Integration Testing Approaches

There are multiple strategies for structuring integration tests. The right choice depends on project size, team structure, and how tightly coupled the modules are.
Big Bang Integration Testing
All modules get combined and tested at the same time. No incremental buildup.
This works on small projects with a handful of tightly connected components. Maybe a startup’s MVP with three or four services. You wire everything together, run the tests, and see what breaks.
The problem shows up at scale. When a test fails in a big bang setup, you have no idea which interface caused the failure. Was it the connection between service A and B? Or between C and D? Took me a while to learn that debugging a big bang failure with 15 modules connected is basically guesswork.
Incremental Integration Testing
Modules get added and tested one at a time, or in small batches. This is the approach most experienced teams prefer because it isolates failures.
Top-down approach: Start from the highest-level modules and work downward. Lower-level modules that are not ready yet get replaced with stubs (simplified stand-ins that return expected data). Good for validating major workflows early. Bad if the lower-level modules have complex behavior the stubs cannot replicate.
Bottom-up approach: Start from the lowest-level modules and work upward. Higher-level modules get replaced with test drivers that simulate calls from above. Good for testing foundational components like database layers and utility services first. The tradeoff is that you do not see the full user-facing workflow until late in the process.
Sandwich (hybrid) approach: Combines both. High-level and low-level modules get tested simultaneously, meeting in the middle. This is how larger teams working under agile methodology tend to operate when they have enough people to parallelize the effort.
| Approach | Starting Point | Uses Stubs/Drivers | Best For |
|---|---|---|---|
| Big Bang | All modules at once | Neither | Small projects, quick validation |
| Top-Down | High-level modules | Stubs for lower modules | Workflow-first validation |
| Bottom-Up | Low-level modules | Drivers for higher modules | Foundation-first validation |
| Sandwich | Both ends simultaneously | Both | Large teams, parallel testing |
Which approach do most teams pick
Honestly, it depends on how the codebase is structured. Monolithic applications lean toward big bang or top-down. Microservices architectures favor bottom-up or contract-based testing (more on that later).
The Gartner Peer Community found that 45% of organizations automate integration testing specifically, making it one of the top three automated test types alongside API testing (56%) and performance testing (40%).
How to Write Integration Tests

Writing integration tests is not the same as writing unit tests with fewer mocks. The mindset has to shift from testing isolated logic to testing the boundaries where components connect.
Identify your integration points
Before writing a single test, map where your modules actually touch each other. These are your integration points:
- API boundaries between services (REST endpoints, GraphQL APIs)
- Database read/write operations
- Message queue producers and consumers (Kafka, RabbitMQ)
- File system interactions
- Third-party service calls
Your integration tests should focus on these boundaries, not on business logic. That is what unit tests are for.
Set up realistic test environments
The test environment has to resemble what runs in production. Otherwise you are testing fiction.
Tools like Testcontainers let you spin up real databases, message brokers, and other dependencies as Docker containers during test execution. Your integration test hits an actual PostgreSQL instance, not an in-memory fake. The difference matters because in-memory databases behave differently than real ones when it comes to connection handling, query execution, and transaction isolation.
Maintaining environment parity between test and production is one of the most overlooked parts of integration testing.
Decide what to mock and what to test live
Mock external services you do not control (payment gateways, third-party APIs, email providers). Their availability should not determine whether your tests pass.
Test real connections to services you own. If your order service talks to your inventory service, test that actual connection. Over-mocking integration tests turns them into glorified unit tests, and that defeats the purpose.
Contract testing tools like Pact sit in the middle. They verify that two services agree on their API contract without requiring both to be running at the same time. Useful for microservices teams where services deploy independently.
Structure tests around data flow
Each integration test should follow data through a boundary. Send input at point A, verify output at point B.
A test for user registration might send a POST request to the registration endpoint, then query the database directly to confirm the user record exists and the password hash was stored correctly. That is a data flow test. It crosses the API layer and the database layer in one pass.
Integration Testing Tools and Frameworks

The right tools depend on your language, framework, and what you are integrating. Here is what most teams actually use.
Java ecosystem
JUnit + Spring Boot Test is the default combination. Spring Boot’s test annotations let you boot a partial application context, wire in real beans, and hit actual endpoints during tests.
Testcontainers pairs with JUnit to launch real Docker containers for databases, message brokers, and other infrastructure. Need a real PostgreSQL or MySQL for your test? Testcontainers spins it up before the test suite and tears it down after. TestNG is another option for teams that prefer its annotation model over JUnit’s.
REST Assured handles HTTP-level integration testing for Java APIs. Clean syntax for sending requests and validating JSON responses. Mockito handles the selective mocking piece.
JavaScript and TypeScript ecosystem
Jest is the most popular test runner for Node.js applications. Combined with Supertest, it handles API integration testing by sending HTTP requests to your Express or Fastify server and asserting on responses.
For frontend development integration scenarios, Cypress provides end-to-end and component-level integration testing in the browser.
Python ecosystem
pytest with fixtures is the standard. Fixtures handle test environment setup and teardown cleanly. Testcontainers for Python brings the same Docker-based approach to Python projects.
.NET ecosystem
xUnit combined with WebApplicationFactory lets you spin up an in-memory test server for ASP.NET Core applications. NUnit is the alternative runner.
Cross-language tools
| Tool | Purpose | Works With |
|---|---|---|
| Testcontainers | Spin up real dependencies in Docker | Java, Python, Node.js, .NET, Go |
| Pact | Contract testing between services | Most languages |
| Postman | API testing and collaboration | Any HTTP API |
| WireMock | HTTP API mocking | Java primarily, standalone server for others |
| SoapUI | SOAP and REST API testing | Language-agnostic |
A Gartner survey found that the most commonly automated test types are API testing (56%), integration testing (45%), and performance testing (40%). The tooling reflects that priority, with API-focused tools like Postman and REST Assured seeing wide adoption.
These tools integrate into CI/CD systems like Jenkins, GitHub Actions, and GitLab CI. Your build pipeline should run integration tests automatically on every commit or pull request. That feedback loop is what makes the whole process work at scale.
Teams practicing test-driven development sometimes write integration test stubs before the module connections even exist. The test defines the expected behavior at the boundary, and the implementation follows.
Common Integration Testing Challenges

Integration tests are harder to get right than unit tests. That is just the reality. The moving parts multiply fast, and every dependency you add introduces a new failure point.
Flaky tests
An estimated 15 to 30% of all automated test failures come from flaky tests, according to CloudQA’s 2025 analysis. These are tests that pass sometimes and fail other times with zero code changes.
Integration tests are especially prone to flakiness. Network timeouts, database connection drops, slow container startups, race conditions in async message processing. Any of these can cause a test to fail when the actual code is fine.
IEEE Transactions on Software Engineering research found that 46.5% of flaky tests are resource-affected, meaning they fail based on available CPU, memory, or I/O rather than actual bugs. That is a lot of wasted debugging time.
Test environment instability
The environment itself is a dependency. If your test database goes down or your Docker daemon runs out of disk space, every integration test fails regardless of code quality.
Teams running tests in shared staging environments deal with this constantly. One developer’s test data corrupts another developer’s test state. The fix is isolated environments per test run, but that costs compute resources and setup time.
Slow execution speed
Integration tests take longer than unit tests. Spinning up databases, initializing service connections, and running HTTP requests adds seconds per test that multiply across a full suite.
Gartner found that 40% of teams run automated tests continuously during development cycles. Slow integration tests create a bottleneck in that loop. If tests take 20 minutes, developers stop running them locally and only rely on CI, which delays feedback.
Managing test data across services
This is the one that sneaks up on you. Each service might have its own database. A test that spans multiple services needs coherent data in all of them.
- A user record must exist in both the auth service and the profile service
- An order test needs matching inventory records in a separate database
- Test data cleanup has to happen across every service or state leaks into the next run
Over-mocking the wrong things
If you mock too many dependencies, your integration test becomes a unit test in disguise. It passes in the test suite but misses real failures at the actual connection points.
Spotify’s engineering team wrote about this directly. They restructured their testing approach around a “testing honeycomb” model that prioritizes integration tests over unit tests for microservices, specifically because the biggest complexity sits in how services interact, not inside each individual service.
Integration Testing in CI/CD Pipelines

Integration tests only deliver value if they run automatically and frequently. Running them by hand before a release is too late.
Where integration tests fit in the pipeline
The standard order in a continuous integration pipeline looks like this:
- Code commit triggers the pipeline
- Linting and static analysis run first (seconds)
- Unit tests execute (seconds to low minutes)
- Integration tests run (minutes)
- Build and package the artifact
- Deploy to staging for system and acceptance tests
Integration tests sit after unit tests because they are slower and more resource-heavy. If a unit test catches the bug, there is no point burning time on a full integration run.
The CD Foundation’s 2024 State of CI/CD report confirmed that 83% of developers now practice DevOps-related activities. Integration testing inside the pipeline is a core part of that practice.
Running tests in parallel
Serial execution of integration tests does not scale. A suite of 200 integration tests running one after another can easily take 30 minutes or more.
Parallel execution strategies:
- Split tests across multiple CI runners (GitHub Actions matrices, Jenkins parallel stages)
- Give each parallel group its own isolated database container
- Use test sharding to distribute tests evenly by execution time, not test count
Docker and containerization for test dependencies
Spinning up real dependencies on every pipeline run used to be painful. Containerization changed that.
Tools like Testcontainers create and destroy Docker containers as part of the test lifecycle. Need PostgreSQL for your test? A fresh container starts before the test and gets torn down after. No shared state, no environment drift.
Zyvora Technologies data shows that continuous deployment practices reduce integration issues by 70% when combined with automated code merging and testing.
Gating deployments on test results
| Gate Type | What It Checks | Blocks If |
|---|---|---|
| Unit test gate | Individual function logic | Any unit test fails |
| Integration test gate | Module interaction correctness | Any integration test fails |
| Security scan gate | Vulnerability detection | Critical or high severity found |
| Performance gate | Response time thresholds | Latency exceeds defined limit |
The integration test gate is the one that catches interface-level regressions before they reach staging. Without it, broken service connections slip through to environments where debugging is harder and more expensive.
Teams that follow behavior-driven development often write integration test scenarios in Gherkin syntax that double as both test code and living documentation for how services should interact.
Integration Testing Best Practices

There is no shortage of advice on testing best practices. Most of it is generic. Here is what actually makes a difference for integration tests specifically.
Test real interactions over mocked ones
Netflix and Spotify both moved away from heavy mocking in their microservices testing strategies. Netflix uses chaos engineering to test real service behavior under failure conditions. Spotify restructured around a testing honeycomb that centers on integration tests at the interaction layer.
The pattern is clear: the closer your test resembles a real service interaction, the more bugs it catches. Mock what you cannot control (third-party APIs). Test everything else against real dependencies.
Use contract testing for microservices
Contract testing validates the agreement between a service consumer and provider without requiring both to be running simultaneously. If Service A expects a JSON response with a “userId” field and Service B renames it to “user_id”, the contract test fails before deployment.
Tools like Pact make this work inside CI pipelines. The consumer publishes expectations to a broker, and the provider verifies against them. If verification fails, the build is blocked.
A 2024 developer survey found that 82% of teams using contract testing reported fewer deployment failures. For teams managing dozens or hundreds of services, this approach replaces many slow integration tests with fast, focused checks.
Keep integration tests focused on boundaries
An integration test should verify what happens at the seam between two components. Not the business logic inside either one.
- Does the API return the correct status code and response structure?
- Does the database write complete and commit successfully?
- Does the message land in the queue with the expected payload?
Business logic belongs in unit tests. Boundaries belong in integration tests. Mixing them up creates tests that are slow, brittle, and hard to debug when they fail.
Fix flaky tests immediately
Ignoring flaky tests is how test suites lose credibility. Developers start clicking “re-run” instead of investigating. Within a few weeks, nobody trusts the test results, and real bugs start slipping through.
Tag flaky tests. Quarantine them in a separate run. Fix them within the sprint, not “whenever someone has time.” The software quality assurance process depends on trustworthy test results.
Clean up test data after every run
State leakage between test runs is one of the most common causes of intermittent failures in integration test suites. Each test should create its own data, run its assertions, and then destroy everything it touched.
Testcontainers solves this at the infrastructure level by giving each test run a fresh database container. For tests that share a persistent database, use transaction rollback patterns or explicit teardown scripts.
Teams practicing source control management well keep their test fixtures and seed data versioned alongside production code, so the test environment stays in sync with the application it is testing.
FAQ on What Is Integration Testing
What is integration testing in simple terms?
Integration testing checks whether separate software modules work correctly when combined. Instead of testing functions in isolation, it verifies the connections between components, like API calls, database writes, and message queue interactions.
What is the difference between unit testing and integration testing?
Unit testing checks a single function using mocks for all dependencies. Integration testing connects real or partially real components and verifies their interaction at boundaries. Both are needed. They catch different categories of bugs.
What are the main types of integration testing?
The main approaches are big bang (all modules at once), top-down (high-level first using stubs), bottom-up (low-level first using drivers), and sandwich (both directions simultaneously). Each fits different project sizes and team structures.
When should integration testing be performed?
After unit testing passes and before system-level testing begins. In CI/CD pipelines, integration tests typically run automatically on every commit, right after unit tests complete and before deployment to staging environments.
What tools are used for integration testing?
Common tools include JUnit and Testcontainers for Java, Jest with Supertest for Node.js, pytest for Python, and xUnit for .NET. Pact handles contract testing. Postman and REST Assured cover API-level integration checks.
Who is responsible for writing integration tests?
Developers write most integration tests, especially in teams following DevOps or agile practices. QA engineers contribute test cases for complex service interactions. The split depends on the organization, but developer ownership is the more common pattern now.
How does integration testing work in microservices?
Each microservice gets tested against the services it depends on. Contract testing tools like Pact verify API agreements between services without running both simultaneously. Testcontainers spin up real dependencies as Docker containers during test execution.
What are common integration testing challenges?
Flaky tests, slow execution, test environment instability, test data management across multiple databases, and over-mocking that hides real connection failures. These problems grow as the number of services and integration points increases.
Can integration testing be automated?
Yes. Most teams automate integration tests inside their CI/CD pipelines using tools like Jenkins, GitHub Actions, or GitLab CI. Automated integration testing runs on every code change and blocks deployment if a test fails.
How many integration tests should a project have?
There is no fixed number. Focus on covering every critical integration point: API boundaries, database layers, message queues, and third-party service connections. Quality and coverage at boundaries matters more than hitting a specific test count.
Conclusion
Knowing what is integration testing is only the starting point. The real value comes from applying it consistently across your software system, with the right approach for your architecture and team size.
Whether you run big bang tests on a small project or contract tests across dozens of microservices, the goal stays the same. Catch interface-level bugs before they reach users.
Pick tools that fit your stack. Automate tests inside your deployment pipeline. Fix flaky tests fast. Keep test data clean.
Integration testing is not glamorous work. But skipping it, or doing it poorly, is how preventable failures make it to production. The teams that invest in solid integration test coverage ship with confidence. The ones that don’t spend their time debugging in staging instead.
- What Happens When You Offload an App on iPhone - May 9, 2026
- How to Use Digital Wellbeing on Android - May 8, 2026
- Why Buyers Trust a Well-Built Data Room - May 7, 2026







