Every local-cloud tool sits somewhere on a spectrum. At one end: mocks that re-implement an API's request/response shape in process. At the other: a digital twin that runs the real backends behind the same API. They look interchangeable in a quickstart. They are not — and the difference is where "passed locally, failed in production" comes from.
What a mock can't see
A mock encodes someone's understanding of a service. That understanding is necessarily incomplete, and it freezes in time. The usual blind spots:
- Derived values — S3 ETags, content hashes, version IDs. A mock fakes them; code that depends on them passes locally and breaks on real cloud.
- Consistency & ordering — pagination tokens, eventual consistency, queue visibility timeouts. Mocks tend to be instantly-consistent, hiding real races.
- Policy evaluation — IAM/Cedar authorization. A mock that rubber-stamps every call won't catch the policy that denies you in production.
- Error semantics — the exact exception, status code and retry behavior your SDK keys off.
What a digital twin does instead
A twin doesn't re-describe the service — it runs a real implementation. Vyomi serves S3 from MinIO, SQL from real Postgres/MySQL, NoSQL from DynamoDB Local and friends, secrets from a real KMS engine, authorization from real Cedar policy evaluation. So the ETag is a real ETag, the denied call is really denied, and the pagination loop is really exercised — with the standard SDKs pointed at one local endpoint.
When a mock is still the right call
This isn't "mocks are bad." For a fast unit test that asserts your logic given a known cloud response, an in-process mock like moto is perfect — it's lighter and runs in microseconds. The mistake is using a mock as your environment: the place you develop, integration-test, and demo. That's the twin's job.
The rule of thumb
Mock the boundary in unit tests; run a twin for everything else. If a green test depends on behavior the mock invented, it's not telling you the truth. See the full local cloud emulator landscape, or read how the endpoint-swap pattern works in one endpoint URL.