Mocking the backend
Some tests need the app to be in a state that is hard to create on demand — a subscription that has expired, a server returning 500, a list with nothing in it, a week of history that has to exist first.
A test can declare the backend conditions it needs. We put them in place for that run, and take them away afterwards.
Mobile tests only, and as setup rather than as steps. API scenarios and mid-test changes are not supported yet — see Limits.
Declaring it
Add a mocks: block beside your steps:
mocks:
- method: GET
path: /v1/subscription
status: 200
body: '{"tier":"expired","renewsAt":null}'
steps:
- action: tap
selector: "accessibility:test-Account"
- action: assert_visible
selector: "accessibility:test-Paywall"
When the test runs, GET /v1/subscription returns that response. The test checks the paywall appears —
which it can now do on any account, on any day, because the test brings its own conditions.
Fields
| Field | Required | Meaning |
|---|---|---|
path | yes | The path to stub, e.g. /v1/subscription |
method | no | GET, POST, … Defaults to matching any method |
status | no | Response status. Defaults to 200 |
body | no | Response body, as a string |
contentType | no | Defaults to application/json |
Why it is setup and not a step
The stubs are declared alongside the steps, not among them. A stub takes no time on the device and does not touch the screen — listing it as a step would put a passing "step" in your report next to real taps, which makes the report harder to read and the test slower to understand.
It is the same reasoning as preconditions: state the test runs against belongs with setup.
What happens around your run
Before the phone starts, the stubs are registered.
If the mock cannot be reached, the test does not run. You get an error naming the mock, and nothing is dispatched. A test that asked for a mock and did not get one would hit the real backend and report a pass or a failure that means nothing — a false green is worse than a clear refusal.
When the run ends, the stubs are removed — whether the test passed, failed, or crashed. A stub left behind would silently change the next test, and that is a hard failure to diagnose, because the test that breaks is not the test that caused it.
Limits
These are real and worth knowing before you plan around them.
Mobile only. API scenarios cannot declare mocks yet.
Setup, not steps. The stubs are fixed for the whole run. You cannot say "sign in successfully, then make the next call fail" — anything that needs a condition to change partway through is not expressible today.
The phone has to be able to reach the mock. This is the big one. A phone on a hosted device farm is on someone else's network, so it can only reach the mock if the mock is published on a public address and the app under test can be pointed at it for the run.
Each run's stubs live behind their own unguessable path — https://<mock host>/r/<runToken> — so the
run token acts as the credential. There is no login on the device, and the stubs disappear when the
run ends. But the app still needs a way to be told that base URL per run, which usually means a debug
build that reads its API base URL from a launch argument.
If no reachable mock host is configured, a run that declares mocks: on a cloud device is refused
rather than run. Letting it proceed would send the app to the real backend and report a pass that
proved nothing.
It fakes the cloud, not the device. A stub stands in for what the backend returns. It cannot stand in for hardware — a sensor, a Bluetooth peripheral, a camera. If your app receives data directly from a device rather than from an API, a mock cannot substitute for it.
When a mock is the wrong tool
Reach for one when the condition lives in a backend response: error codes, edge-case payloads, states that are slow or destructive to create for real.
Do not reach for one when the thing you are testing IS the backend. A test that passes against a stub proves the app handles that response — it proves nothing about whether your server ever sends it. Keep some tests running against the real thing.