A practical integration testing plan for business applications
Effective integration testing checks whether connected parts of an application work together as expected. It can exercise a service with its database, an application with an external API, or a workflow that passes messages between systems. These tests reveal problems that isolated logic tests cannot always detect.
A pricing calculation can pass its unit tests while an invoice still fails because the database mapping is wrong. Likewise, two services can each work alone but disagree about a field or a retry.
This article explains how to choose integration boundaries, prepare test data, and test successful and failed workflows. It also covers real dependencies, mocks, environments, and the evidence a release team needs.
What integration testing adds
A unit test usually checks a small piece of logic in isolation. An integration test checks a connection between components. An end-to-end test follows a broader journey through the system.
The boundaries vary by application and team, so define what each suite includes. Labels alone do not tell you which real dependencies a test exercises.
Microsoft’s integration testing guidance for ASP.NET Core provides an example of testing application behavior with supporting infrastructure. The broader principle applies across stacks: verify the boundary where independently correct parts must cooperate.
Use a layered approach. Keep simple business-rule cases in fast unit tests, then use integration tests for behavior that depends on a real connection or infrastructure feature.
Start with a business risk map
List the application’s important data and service boundaries. Include databases, file storage, APIs, message queues, authentication, scheduled jobs, and external providers.
Next, connect each boundary to the business work it supports. A failed analytics export and a duplicated payment need different levels of attention.
For each critical workflow, ask what can go wrong at the boundary. Common answers include a changed field, a missing permission, a timeout, a duplicate event, or a partial update.
Use that map to choose the first tests. Do not attempt every possible combination before protecting the highest-impact behavior.
For example, an order workflow may need strong checks around stock reservation and invoice creation. A display-only preference can use a lighter set of checks if its impact is lower.
Define observable outcomes before writing tests
A test needs a result it can inspect. A successful HTTP response may not prove that the intended business record exists or that a background action completed.
State the expected outcome in business terms, then identify the evidence. For example, an approved order should create one invoice linked to the correct customer and amount.
Also check unwanted effects. The same action should not create a second invoice, expose another account’s record, or leave stock reserved after a rejected order.
This makes the test more meaningful than checking only that a function returned without an error. It also helps business and technical staff agree on what the integration must preserve.
Test the database boundary with realistic behavior
Database integration tests should cover mappings, constraints, transactions, and queries that depend on the database’s actual behavior.
An in-memory substitute can be useful for some tests, but it may not reproduce the production engine’s rules. Differences in constraints, sorting, transactions, and supported queries can matter.
Therefore, use the real database engine for the cases where those differences affect correctness. A disposable environment can help keep runs isolated and repeatable.
Test a transaction that fails partway through. Confirm that the intended changes roll back or leave a documented recoverable state. Also test competing updates where the workflow relies on concurrency control.
Keep schema migration checks in scope when a release changes data structure. The new application may need to work with records created before the migration as well as new ones.
Test APIs at the contract and behavior levels
An API contract describes fields, types, status codes, and other expectations. A contract check can reveal incompatible changes before a full workflow runs.
However, a matching shape does not prove correct business behavior. An API can return the expected fields with the wrong customer or amount.
Oxen’s contract testing guide explains this complementary layer. Use contract checks alongside integration tests that verify outcomes.
For an external API, test authentication, pagination, missing fields, rate limits, and documented error responses. Include a response that is valid but empty, since “no results” should differ from a connection failure.
Also test version assumptions. If a provider adds a field or changes an optional value, the application should behave according to its compatibility rules rather than fail unexpectedly.
Choose real dependencies and mocks deliberately
A mock or fake gives controlled responses without calling the real dependency. It helps create rare failures and keeps tests fast and predictable.
However, a mock can drift from reality. If it always returns the format developers expect, it may hide the exact incompatibility the integration test should catch.
Playwright’s API mocking documentation shows how browser tests can control network responses. Use that control to test interface states, while retaining separate evidence that the real service connection works.
A practical mix often includes local fakes for many cases, a real test database for database behavior, and a smaller set of provider-sandbox checks.
Oxen’s service virtualization guide covers realistic simulated dependencies. Keep their limitations visible so a passing simulated test does not imply that the live provider has also passed.
Exercise retries, timeouts, and partial success
Network failures create uncertainty. A service may perform an action but lose the response. The caller then has to decide whether retrying is safe.
Test that case directly. For an invoice request, simulate a lost response after the destination saves the invoice. A retry should follow the agreed duplicate-prevention rule.
Also test a dependency that responds too slowly. Confirm the timeout, user message, and eventual cleanup. An abandoned request should not leave resources or business state stuck indefinitely.
Partial success needs a repair path. If a workflow updates one system and fails on the next, define how it resumes, compensates, or enters a review queue.
Do not assume a successful database transaction covers an external API call. These systems have separate failure boundaries, so the test should reflect the actual workflow design.
Check asynchronous workflows without arbitrary waits
Queues and background jobs introduce a delay between request and result. Tests need to wait for a meaningful condition rather than pause for a fixed interval and hope the work finishes.
For example, poll for the expected record or consume a test-visible completion event within a bounded deadline. On failure, capture enough state to show whether the job was missing, delayed, or rejected.
Test repeated and out-of-order messages where the system can encounter them. A later update should not accidentally revert a record when an earlier event arrives late.
Also check the failure queue or equivalent repair mechanism. A rejected job should preserve useful context and remain accessible to the operating team.
Keep test identifiers unique so concurrent runs cannot consume one another’s messages or mistake another run’s record for success.
Prepare test data that reveals edge cases
Use known fixtures with clear meanings. Include records that exercise normal behavior, boundary values, missing optional fields, and invalid relationships.
For multi-tenant applications, add at least two accounts. A request from one account should not read or change the other’s data even if it supplies a valid record identifier.
Avoid relying on live customer records. Use suitable synthetic or appropriately protected data that reproduces the relevant structure and conditions.
Reset state between tests. A suite that passes only in a particular order is hard to trust and difficult to diagnose.
Oxen’s test data management guide covers the supporting processes for preparing and maintaining useful datasets.
A worked example: order approval to invoice
Consider a business application that approves an order and sends invoice data to an accounting service. The integration has several important outcomes.
First, an authorized user should approve an eligible order. The system should then submit the correct customer, lines, and totals and store the returned invoice reference.
Next, test a rejected accounting response. The order should show a clear pending or failed integration state, according to the design, without pretending that the invoice exists.
Then simulate the uncertain case: the accounting service creates the invoice but the response disappears. Retry the request and verify that the system recognizes the existing operation rather than creating a duplicate.
Finally, test a later correction. The application should follow an agreed update or credit workflow instead of silently overwriting a financial record. The exact rule belongs to the business contract for the integration.
Run tests at the right points in delivery
Fast, deterministic integration checks can run on normal changes. Slower provider checks may run at other defined points, especially when they depend on shared sandboxes or external limits.
Separate suite responsibilities so teams know what a passing build establishes. A local suite may prove database behavior while a release check proves provider connectivity.
Record configuration and dependency versions. If a sandbox changes, the team needs to distinguish an application regression from an environment issue.
Also keep failures actionable. Logs should identify the test, relevant request, and failed expectation without exposing credentials or private payloads.
Reduce flaky tests at the cause
A flaky test passes and fails without a meaningful application change. Common causes include shared state, timing assumptions, unstable test data, and uncontrolled external services.
Investigate those causes rather than simply rerunning until the suite turns green. Retries can help reveal a transient pattern, but they should not erase the underlying failure.
Assign an owner and a repair plan for unreliable tests. If a test must temporarily stop blocking releases, record the risk and the replacement check.
Review suite value regularly. Remove duplicate checks, strengthen weak assertions, and keep tests aligned with current business behavior. The goal is useful release evidence, not the largest possible test count.
Keep environment failures distinct from product failures
Software integration testing depends on a working test environment. If a shared database is unavailable, several tests may fail before they reach the behavior they intend to check.
Add a clear setup check and preserve its result. The team should know whether a test found an application defect or could not establish its required conditions.
However, avoid dismissing every external failure as a test problem. If the application handles that dependency in production, its response to unavailability may itself need a test.
Keep configuration close enough to production to exercise relevant behavior, while using safe credentials and isolated data. Document intentional differences, such as a simulated payment service, so reviewers understand the limit of the evidence.
Finally, track test duration. A suite that becomes too slow may delay feedback and encourage bypasses. Improve isolation, remove redundant cases, and place slow checks at suitable delivery stages without losing coverage of critical boundaries.
Frequently Asked Questions
Does integration testing replace unit testing?
No. Unit tests cover isolated rules efficiently, while integration tests check connected behavior. Use each where it provides the clearest evidence with reasonable effort.
Should integration tests call production services?
Normally use controlled test environments and provider sandboxes. Production checks need a separate, carefully scoped operating plan that avoids unintended business actions.
How much integration coverage is enough?
Prioritize critical boundaries and known failure modes. Coverage should reflect business risk, recent changes, and incident history rather than a universal percentage.
Test the boundaries that carry business risk
A useful integration testing plan proves more than a successful connection. It checks the right records, permissions, and failure outcomes across real boundaries. Start with one critical workflow, make the tests repeatable, and expand where the evidence will improve release decisions.
Make business integrations easier to trust
Oxen Softwares can help design and test application connections with clear contracts, useful failure states, and repeatable release checks.