In a Cypress `cy.intercept()` handler, how do you build the reply from the request?
answer
- A function instead of an object
- It runs once per matching request
- Everything you need is on req
- Shorthand mirrors status, body, headers
- Return a promise to make Cypress wait
basics
~20 sPass a function instead of a StaticResponse. Cypress calls it per matching request with the request object, so you read req.query, req.body, req.url or req.headers, compute a payload, and call req.reply() with it - or with a status code, body and headers.
solid answer
~40 sGive `cy.intercept()` a function as its last argument. Cypress invokes it in the browser for every matching request, passing the intercepted request, so the reply can be derived from `req.query`, `req.body`, `req.url`, `req.headers` or `req.method`. Call `req.reply()` with a `StaticResponse` — `req.reply({ statusCode: 200, body: forecastFor(req.query.station) })` — or use the positional shorthand `req.reply(body)`, `req.reply(body, headers)` and `req.reply(statusCode, body, headers)`. Returning a promise makes Cypress wait before the request proceeds, which is how an asynchronously computed body works. Edits you make to `req` itself — `req.headers`, `req.body`, `req.url`, `req.alias` — are merged into the outbound request instead of answering it. Two traps: bare `req.reply()` with no argument sends the request to the real server rather than stubbing it, and calling both `req.reply()` and `req.continue()` fails, because a request can only be completed once.
code
javascript · 18 linesconst forecasts = {
KSEA: { tempC: 14, summary: 'Rain' },
KPDX: { tempC: 19, summary: 'Cloudy' },
}
cy.intercept('GET', '/api/forecast*', (req) => {
const station = req.query.station
if (!forecasts[station]) {
return req.reply(404, { message: `unknown station ${station}` })
}
req.reply({
statusCode: 200,
body: { station, ...forecasts[station] },
headers: { 'x-stubbed-by': 'cypress' },
})
})go deeper
Know that the last argument can be a function, and that calling req.reply() with an object is what actually sends a response back to the application.
Explain when a computed reply beats a StaticResponse, name the shorthand forms of req.reply(), and say what bare req.reply() does.
Diagnose the completion errors in a real spec: replying twice, replying after the handler returned, and an async handler that never returned its promise.
Set the boundary for how much request-dependent behaviour a suite is allowed to compute in handlers before it becomes an unversioned copy of the backend.
## What the handler is Pass a function as the last argument to `cy.intercept()` and Cypress calls it once per matching request, in the browser, before the request leaves. Its single argument — conventionally `req` — is the intercepted request, and it is both readable and writable: ```js cy.intercept('GET', '/api/forecast*', (req) => { // req.method, req.url, req.query, req.headers, req.body, req.resourceType }) ``` This is the mechanism behind "computed" replies. A `StaticResponse` is fixed at the moment you write the route; a handler is evaluated per request, so the reply can depend on which station the dashboard asked for, what the user typed, or how many times the route has fired already. ## Reading the request | Property | What it holds | |---|---| | `method` | the HTTP method as a string | | `url` | the full request URL | | `query` | the query string parsed into an object | | `headers` | request headers as a map of strings | | `body` | the request body; a parsed object when JSON was sent, a string or buffer otherwise | | `resourceType` | `fetch`, `xhr`, `document`, `image` and friends | So a handler that answers each station with its own forecast reads `req.query.station` and picks the payload from a lookup: ```js const forecasts = { KSEA: { tempC: 14 }, KPDX: { tempC: 19 } } cy.intercept('GET', '/api/forecast*', (req) => { req.reply({ body: forecasts[req.query.station] ?? {} }) }) ``` ## Replying `req.reply()` takes the same `StaticResponse` shape as the route argument, plus positional shorthand: - `req.reply(body)` — equivalent to `req.reply({ body })` - `req.reply(body, headers)` — body plus headers - `req.reply(statusCode, body, headers)` — a number first is always the status code - `req.reply({ statusCode, body, headers, fixture })` — the full object form Two behaviours surprise people: 1. **Bare `req.reply()` does not stub.** With no argument it ends the request phase and sends the request to the destination server, exactly like `req.continue()`. It is a propagation control, not an empty reply. 2. **`req.reply(fn)` is not a reply at all.** As of Cypress 16, passing a function is forwarded to `req.continue(fn)` for compatibility with older specs. Write `req.continue()` when you mean the response phase; it says what it does. ## Asynchronous handlers If the handler returns a promise, Cypress awaits it before the request continues. That is how a reply can depend on something you have to fetch first: ```js cy.intercept('GET', '/api/alerts', (req) => { return loadAlertsFor(req.query.region).then((alerts) => { req.reply({ statusCode: 200, body: alerts }) }) }) ``` Note that this is plain JavaScript inside the handler, not the Cypress command queue — you cannot call `cy` commands here and expect them to interleave with the request. ## Editing the request instead of replying to it Not every handler exists to stub. Any change you make to `req` is carried to the remaining matching routes and then merged into the real outbound request: - `req.headers['x-test-run'] = 'true'` adds or replaces a request header. - `req.body = { station: 'KSEA' }` rewrites the payload the server will receive. - `req.url = req.url.replace('/v1/', '/v2/')` retargets the request. - `req.alias = 'forecastKSEA'` names *this* request at request time, so a dynamic subset of a busy route can be identified individually rather than by the route's static alias. A handler that only does this and returns is still a spy: the request goes out and is answered by the real server. ## The rules that bite - **A request can be completed once.** Calling `req.reply()` and `req.continue()` in the same handler fails with "`req.reply()` and/or `req.continue()` were called to signal request completion multiple times, but a request can only be completed once." - **Complete it before the handler finishes.** Calling `req.reply()` from a callback that runs after the handler has already returned fails with a "was called after the request handler finished executing" error. If the value is asynchronous, return the promise so Cypress waits. - **Completing stops propagation.** Once a handler replies or continues, other matching routes do not run. That is a feature when you deliberately override a broader route, and a puzzle when you did not intend to.
- Can a Cypress route handler call `cy` commands to build its reply?No. The handler is plain JavaScript running outside the command queue, so `cy` commands enqueued inside it do not run in step with the request and the request will not wait for them. Compute the value beforehand and close over it, or return a promise from the handler so Cypress awaits that instead.
- What does setting `req.alias` inside a Cypress handler achieve?It names that individual request rather than the route. A busy route can then have one particular request identified separately from the rest of its traffic — assign the alias conditionally, for example only when `req.query.station` matches the one under test, and that single request/response cycle becomes addressable while the route's own alias still covers the others.
saying these in an interview costs you the question
- Thinks bare req.reply() stubs an empty response
- Calls both req.reply() and req.continue()
- Uses cy commands inside the handler
- Replies asynchronously without returning a promise
- Believes the handler runs once per test, not per request