Quick answer: Contract drift between a mock and an API is the gap that builds up when a mock keeps returning the responses it was configured with while the real API changes on its own. A mock is a snapshot frozen at one point and it does not run the backend's logic, so field renames, new status codes, changed types, and new constraints in the live service never reach it unless someone updates it. The result is tests that pass against the mock and fail against the real API later.

Why a mock and an API drift apart

A mock is static by design. It answers from fixed response overrides, while the real backend is code that keeps shipping. Those two things only stay in sync through a shared contract, usually an OpenAPI document, and that link breaks in ordinary ways. The backend changes without a matching spec update, or the spec is updated but the mock is never regenerated, or the mock holds hand-written example data that quietly conflicts with the schema. Because the mock does not execute business logic, it cannot notice that the service behind it moved. It returns the same answer after a backend deploy as before, which is exactly what makes the divergence silent.

The common forms of drift

The differences show up in a handful of recurring places, most of them invisible while you only look at happy-path responses.

Dimension What the stale mock returns What the real API now does
Fields The old field names and shape Renamed, removed, or added fields
Types A number or a fixed date string A string, a different timestamp format
Enums and status A known set of values New status values or error codes
Nullability and rules Always-complete example data Fields that can be null, new required inputs
Error paths Only the success case 4xx and 5xx bodies with a different shape
Pagination The old wrapper and cursor fields A restructured envelope or paging contract

Why drift stays hidden until integration

Tests that run against a mock assert against responses the mock itself supplies, so they stay green no matter what the real backend does. A frontend built on those fixed responses can look finished, then break the first time it meets a live field that was renamed or an error body it never saw. The failure is delayed to the integration stage, when the cause is harder to trace because the mock never raised an alarm. A green mock suite proves the client handles the preset responses, not that those responses still match the service.

How to detect and narrow drift

Treat the spec as the single source of truth and keep both sides pointed at it. Generate or validate the mock from that document, and regenerate it whenever the spec changes rather than editing overrides in isolation. Then run contract checks against the real API: send real requests and compare the live responses to the schema, so a structural change in the service surfaces in CI instead of at release. Keep a small set of scenario tests aimed at the real backend as a safety net, since mock-only tests cannot see the live service at all. A desktop API workspace helps here because it supports both a local mock and real requests with assertions in one place, so you can run the same checks against the mock and the live API and read the difference directly. Powerduck works this way, which makes the comparison a concrete step rather than an assumption.

Honest limits

A local mock runs in the desktop main process and does not replace or deploy the real backend. It shows how a client handles a response you defined, not whether the backend will actually produce it, because none of the service's logic runs. Schema validation catches structural drift well, but it can miss semantic change where a field keeps its name and type while its meaning shifts. Importing a spec does not stand up a backend, and a passing mock run is not evidence about the live system, so real-backend checks remain necessary.

Frequently asked questions

Is contract drift the same as a bug? Not exactly. Drift is a mismatch between the mock and the current API, which can come from a legitimate backend change that the mock simply did not absorb. It becomes a bug when code ships trusting the stale mock.

Can a mock that returns 200 still be drifted? Yes. A 200 says the mock answered, nothing about whether its fields, types, or error cases match the real API. Compare the response shape against the schema and the live service.

How often should I check the mock against the real API? On every spec change and in CI against the real backend, rather than on a manual schedule. Catching a renamed field at build time is cheaper than at integration.

Does regenerating the mock erase my custom responses? It can overwrite overrides, so keep custom data aligned with the schema and reapply only values that still conform, instead of preserving stale examples.

The bottom line

A mock is a frozen model of the API, not a live copy, so drift is the default state unless you actively keep it tied to the spec and the real service. Generate it from one contract, recheck real responses against the schema in CI, and keep real-backend scenario tests as the check the mock cannot provide. For more on this approach, see the API mocking guide hub.