What Is Dependency Mocking and How Does It Work in Integration Testing
Every service your application calls is a liability in testing. Not because the service is unreliable- although that is sometimes true- but because every real service call in a test introduces a dependency on something outside the test’s control. The payment service needs to be running. The database needs to be in a specific state. The third-party shipping API needs to respond within the test timeout. Any of these can fail for reasons unrelated to whether the code being tested is correct, and when they do, the test fails in a way that tells you nothing useful.
Dependency mocking addresses this by replacing real service calls with controlled substitutes. The substitute returns what the test configures it to return. The test runs without any real service needing to be available. The failure modes caused by external service state are eliminated, which means when a test fails, it fails because of the code, not because of the environment.
Why Integration Tests Make This Harder Than Unit Tests
Unit tests mock dependencies at the function or class level. A function that calls a repository gets tested with a mock repository that returns controlled data. The scope is narrow. The mock is close to the code. The configuration is straightforward.
Integration tests operate across service boundaries. The order service calls the payment service over HTTP. The inventory service publishes to a message queue. The notification service reads from a database. Each of these is a different kind of dependency with a different protocol, a different failure mode, and a different set of behaviors that the mock needs to represent accurately.
HTTP-level mocking for service calls is where most integration test dependency mocking happens. A tool intercepts the outbound HTTP request from the order service to the payment service and returns a pre-configured response. The order service receives the response as if the real payment service had returned it. The test validates how the order service processes that response without the real payment service needing to be involved.
This sounds simple. The complication is that the pre-configured response needs to accurately represent what the real payment service actually returns- not just structurally, but behaviorally. The specific fields. The error formats. The response headers. The edge case behaviors. Every gap between the mock and the real service is a gap between what the test validates and what production will encounter.
What a Dependency Mock Actually Contains
A dependency mock for integration testing is not just a status code and a response body. The mock needs to represent the full behavioral contract between the calling service and the dependency.
The response schema is the most visible part- which fields the response contains, what type each field is, how nested objects are structured. But the behavioral contract goes further. How does the service respond when authentication fails? What does the error response look like when the input is invalid versus when the service itself encounters an internal error? Does the service return different response headers in different scenarios? What happens when the request is rate limited?
Each of these is a behavior the calling service’s code needs to handle correctly. If the mock does not represent a behavior, the code handling that behavior never gets tested. The test suite accumulates coverage gaps that look like covered scenarios because a mock exists for the endpoint, but are actually uncovered scenarios because the mock only represents one behavioral state of the endpoint.
The Accuracy Problem That Appears After Setup
Setting up dependency mocks for integration tests is the part most teams do once and consider complete. The real cost comes later.
Get Sancharini Panda’s stories in your inbox
Join Medium for free to get updates from this writer.
Upstream services change. The payment service ships a release that restructures its error response format. The inventory service adds a required field to its response that the consuming code needs to handle. The notification service changes the status codes it returns for specific conditions. Each of these changes makes some portion of the existing mock configuration less accurate.
The mocks do not update themselves. They continue returning what they were configured to return. The integration tests continue passing because the tests assert against the mock configuration, not against current real service behavior. The gap between mock behavior and real behavior grows with each upstream deployment that the mock does not account for.
This is the specific accuracy problem that dependency mocking in integration testing creates and that most setup-focused guides do not address. The test suite looks healthy. The coverage numbers look good. The CI pipeline is green. Somewhere in the gap between what the mocks return and what the real services now return, there is a production failure waiting for the next deployment to surface it.
How Teams Discover Mock Drift
Mock drift surfaces in two ways. The first is a test failure after an upstream service change where the mock’s response structure no longer matches what the real service returns and someone notices the discrepancy during investigation.
The second is a production failure where the application encounters real service behavior that the mock never represented. The test for that code path passed because the mock returned a response the code handled correctly. The real service returned something different. The code that was tested against the mock could not handle what the real service sent.
The first discovery is cheap. The second is expensive. The cost difference is what makes mock accuracy a meaningful engineering concern rather than a theoretical one. Teams that treat dependency mocking as a setup task and never revisit mock accuracy tend to experience more of the second kind of discovery and fewer of the first.
What Keeps Mocks Accurate Over Time
The practices that keep dependency mocks accurate address the drift problem directly rather than assuming the initial setup remains valid indefinitely.
Validating mocks against real staging service responses before each release catches drift before it reaches production. A comparison between what the mock returns and what the current staging service returns surfaces every field that has changed, been added, or been removed since the mock was configured.
Connecting mock refresh to upstream deployment events catches drift at the moment it occurs rather than at the next release cycle. When the payment service deploys a new version to staging, that event is the trigger for validating and updating any mocks that represent payment service behavior.
Keploy implements this through kernel-level eBPF traffic capture during recording sessions against staging environments. Instead of a developer specifying what the payment service returns, the recording session captures what it actually returns after its most recent deployment. The generated mock configurations reflect observed post-deployment behavior rather than developer interpretation of documentation or a previous version’s responses. When the payment service changes behavior after its next deployment, a new recording session captures the updated behavior and the comparison between old and new configurations surfaces exactly what changed- which tells the team whether the change requires application code updates or whether the mock can be refreshed directly. For dependency mocking in integration testing across many upstream services on independent deployment schedules, this changes the maintenance model from a reactive task triggered by failures to a proactive process triggered by deployment events.
The Question Integration Tests Should Be Able to Answer
An integration test suite with dependency mocking should be able to answer a specific question: if this code deploys to production right now, will it handle what the upstream services actually return?
When mocks were configured months ago and upstream services have been deploying on their own schedules since, the answer the test suite gives is something different: will this code handle what the upstream services returned when the mocks were written?
These are not the same question. The distance between them grows with time and upstream deployment frequency. Recognizing that distance, and treating mock accuracy as an ongoing property to maintain rather than a one-time configuration to complete- is what determines whether a dependency mocking investment in integration testing produces reliable answers to the first question or only unreliable answers to the second.











