Third-party APIs raised on a laptop as emulators built from their own published API, answering by scenario - a provider that returns 5xx, one with the wrong account settings, timeouts, duplicated callbacks. A proposal; nothing is built yet.
👋 Hi, I'm Anton - a software engineer. These days I work on a platform: contracts, generated code, data paths and the tooling around them. Running notes live on my GitHub: github.com/brilliant-almazov.
This whole part is a proposal. QA, the platform and DevOps are working it out; no provider emulator is built. What I know about how our services reach their providers, I know by reading code.
This is part 8 of the series on testing cross-zone, cross-datacenter and cross-provider behaviour on a laptop. Maybe you already do it better, maybe you'd do it another way - this is how it is going, and where it stops.
The calls that leave
Part 7, Clouds On A Laptop, put cloud services on a laptop. The last part is about the calls that leave a system for someone else's: third-party providers - a results provider, a data provider with callbacks, a read-only console API. I name them only by role.
These calls are the ones tested least. A provider in a test is either the real one - slow, costly, and impossible to make fail on purpose - or a mock inside a unit test that agrees with whatever the code expected. Neither shows what happens when the provider misbehaves in the way providers actually do.
The proposal: raise every provider in the local environment as an emulator. Services call it as they would call the provider; the emulator answers by scenario. That makes provider behaviour a separate class of test scenarios, next to the failure domains of the earlier parts.
Emulated or real
Each provider gets a mode: emulated or real. Real is expensive, and for now it is forbidden on the stand.
The mode is per provider, so one can be switched to real for a single check while the rest stay emulated - when the owner allows it.
Built from the provider's own API
An emulator is not written from what our code expects. It is built from the provider's official API: from its OpenAPI where the provider publishes one, otherwise from its documentation. The difference matters - an emulator built from our expectations would only ever confirm them.
The catalogue of an emulator covers more than the calls a request makes. A sending service also calls the same provider in the background - polling a balance or a tariff, re-sending a callback - and those calls belong in the emulator too, or the stand will try to reach the real provider behind everyone's back.
A scenario on top
On top of the API sits a scenario. The first ones QA has in mind:
- the provider returns 5xx for one upstream while the others answer;
- a provider with the wrong account settings - valid API, wrong answer;
- timeouts;
- duplicated callbacks - the same event delivered twice.
The last is the one I would bet on finding something. Only one of our providers uses callbacks - the data provider with callbacks, by the code - and a callback that arrives twice must be accepted once.
Three shapes of provider
Not every provider is hard. By role:
| Provider | Shape | What an emulator needs |
|---|---|---|
| a results provider | request and response | the API and the scenarios |
| a data provider with callbacks | request, then callbacks into our webhook | the API, the callbacks, duplicates and delays |
| a read-only console API | reads only | the API, and a stub of whatever issues its tokens |
Three ways to point a service at it
How a service reaches an emulator depends on how it names its provider. By the code, there are three cases, cheapest first:
- one environment variable - the read-only console API's base URL is an ENV; set it and the service talks to the emulator;
- swap the config file - a sending service keeps provider base URLs as literals in a YAML config with no ENV, but the whole config file is swappable on the stand, so its stand copy points the base URL at the emulator over http; no new ENV, no code change;
- a network alias and a local CA - a service whose provider URLs are generated literals, with no ENV and no swappable config, reaches the emulator through stand network aliases on the provider's hostnames, plus a stand-local CA in its trust store, so TLS still verifies.
The third is the form QA already wrote down as the local provider stub in Every Verdict Carries A Trace - aliases, a local CA, an internal network with no way out, and "0 outbound connections" as the acceptance line. It is the fallback that works for any service, and the most work.
What an emulator must also stub
The read-only console API looks like the simplest case, and it is - with one catch. The service never does the provider's OAuth itself: its tokens come from a separate auth service over gRPC. So the stand needs two things, the provider emulator and a stub of that auth service.
That is the general lesson: an emulator covers a provider, not a call. Whatever the call depends on - its tokens, its callbacks, its background pollers - comes with it.
What the world uses
None of this needs a new tool. The world has several, each from its own documentation:
-
WireMock - stubs as JSON mappings, with delays (
fixedDelayMilliseconds) and faults:CONNECTION_RESET_BY_PEER,EMPTY_RESPONSE,MALFORMED_RESPONSE_CHUNK,RANDOM_DATA_THEN_CLOSE(stubbing, faults); -
Prism - mocking and contract testing from OpenAPI v2 and v3.x, Apache-2.0,
prism mock <spec>; in Docker it needs-h 0.0.0.0(Prism); -
Mockoon - a CLI that runs mock APIs headless, MIT,
mockoon-cli start --data(Mockoon CLI); - Hoverfly - open-source API simulation with six modes; Capture records traffic as a proxy, and the capture is exported and re-imported for Simulate (modes, capture);
-
MockServer - a self-hosted mock for HTTP, HTTPS and gRPC, with expectations as
httpRequest/httpResponse, and a proxy that records for later replay (MockServer).
A scenario in WireMock's own form:
{
"request": { "method": "GET", "url": "/fault" },
"response": { "fault": "CONNECTION_RESET_BY_PEER" }
}
Prism fits "built from the provider's own API" when an OpenAPI exists. WireMock fits the fault scenarios. Hoverfly and MockServer fit the later step below. Which one is chosen is not decided.
Data
The emulators would answer with synthetic data for now. Later, the plan is real responses exported from production through the admin API, so the emulator's answers have production's shape - planned.
An emulator sees what tracing missed
One side effect is worth naming. Once every provider call lands on an emulator on the stand, it is easy to see which of those calls has no client span of its own. A provider call over HTTP without a client span is an observability gap - and an emulator exposes it for free.
In a zone, under chaos
An emulator is a container. Like the cloud emulators of part 7, it can be placed in a zone of the planet from part 3, and then it falls under the same chaos: take its provider's sites down, degrade the link to it, cut it off. A provider outage becomes one more failure domain.
The series in one picture
Eight parts, one idea each: One Login For Every Stand, Four Failure Domains, A Planet On A Laptop, Placement In Every Trace, How Others Test Cross-Zone Locally, Every Verdict Carries A Trace, Clouds On A Laptop, and this one. The built parts are the login stand, the topology emulation and the verdict with its trace. The rest is in review, planned or proposed, and each part says which.
What I do not claim
- Not that any provider emulator exists. This part is a proposal.
- Not which tool is chosen. That is not decided.
- Not that production responses are exported. That is planned.
- Not the names of the providers or what they provide. Roles only.
What of this is method
- Emulate from the provider's API, not from your code. An emulator built from expectations only confirms them.
- An emulator covers a provider, not a call. Tokens, callbacks and pollers come with it.
- Cheapest pointer first. One ENV, then a config file, then an alias and a local CA.
Cross-anything on a laptop - Part 8. This closes the series.
If you emulate third-party APIs, do you build them from the provider's spec or from your own expectations? If you test duplicated callbacks, how did you make the provider send one twice? If you record and replay provider traffic, how do you keep it fresh? Which provider would you rather see misbehave on a laptop than in production?










Top comments (0)