skip to content

In Cypress, when is `cy.intercept()`'s object argument read as a StaticResponse?

level: middleimportance: nice to knowfreq 32%

answer

  1. Cypress has to guess what you meant
  2. It looks at the object's keys
  3. One shared key changes everything
  4. Arrays and strings are never options
  5. Wrap it in body to be sure

basics

~20 s

Whenever the object shares a key with the StaticResponse shape - body, fixture, statusCode, headers, forceNetworkError, delay, throttleKbps or log. An object with none of those keys is wrapped as the response body instead, and an array is always the body.

solid answer

~40 s

Cypress inspects the object's keys. If it carries any `StaticResponse` property — `body`, `fixture`, `statusCode`, `headers`, `forceNetworkError`, `delay`, `throttleKbps` or `log` — the whole object describes the response and is validated as such. If it carries none of them, Cypress assumes you handed it the payload and wraps it as `{ body: yourObject }`. Strings and arrays are always bodies; an empty `{}` is read as a `StaticResponse`. The trap is a real payload that owns one of those names: `cy.intercept('/api/alerts', { title: 'Flood', body: 'Move now' })` sends only the string `Move now`, because the `body` key won. Writing `{ body: payload }` explicitly on every stub removes the guess and reads the same way everywhere.

go deeper

for a junior

Get in the habit of writing the wrapper form with an explicit body property. It is never wrong, and it means you never have to remember this rule under pressure.

for a middle

Be able to name the reserved keys and explain why an array and a string are unambiguous while a plain object is not.

for a senior

Recognise the symptom in a real suite: a stubbed route that returns a bare string or an empty payload, with no error anywhere, because a field name collided with the response shape.

for a principal

Decide whether the team writes stubs in the explicit form as a reviewable convention, so a backend adding a field named headers cannot quietly rewrite a test's fixtures.

## One argument, two possible meanings When you pass an object as the response argument to `cy.intercept()`, Cypress has to guess what you meant. You could be describing the *response* — status code, headers, body, fixture — or you could be handing it the *payload itself* and expecting it to be serialised as JSON. It cannot be both, and there is no flag to disambiguate, so Cypress uses a key test. The rule is simple and worth memorising: **if the object shares at least one key with the `StaticResponse` shape, the whole object is treated as a `StaticResponse`. Otherwise Cypress wraps it as `{ body: yourObject }`.** ## The reserved keys These are the property names that flip the interpretation: - `body` - `fixture` - `statusCode` - `headers` - `forceNetworkError` - `delay` - `throttleKbps` - `log` Two shapes are never ambiguous: - A **string** is always a body. `cy.intercept('/api/alerts', 'no alerts')` sends that text. - An **array** is always a body. `cy.intercept('/api/stations', [{ id: 'KSEA' }])` sends the list, even though an array technically has no keys to test. An **empty object** goes the other way: `{}` is treated as a `StaticResponse` that happens to specify nothing, not as an empty JSON body. ## What goes wrong on a weather dashboard The trap fires when a real payload legitimately owns one of those names. An alert object with a title and a body of text is entirely natural: ```js // intended: return one alert object as JSON cy.intercept('GET', '/api/alerts', { title: 'Flash flood warning', body: 'Move to higher ground', }) ``` Because the object has a `body` key, Cypress reads it as a `StaticResponse`. The application receives the string `Move to higher ground` as the entire response body, and the `title` you meant to send is not part of it. Nothing errors, the route is stubbed, and the dashboard renders an alert with no headline — a failure that looks like an application bug. The same shape bites a station record with a `headers` array, a scheduling payload with a `delay` field, or any log-shaped object with a `log` property. ## Making the intent explicit The fix costs four characters and removes the guess entirely: ```js cy.intercept('GET', '/api/alerts', { body: { title: 'Flash flood warning', body: 'Move to higher ground', }, }) ``` Writing `{ body: ... }` on every stub is a good house rule even for payloads that could never collide, because it makes every route read the same way and survives a backend that later adds a field named `headers`. ## When Cypress throws instead of guessing wrong Once an object is treated as a `StaticResponse`, its properties are validated, and a payload that merely *looks* like one usually fails loudly rather than quietly: | Mistake | What Cypress says | |---|---| | `{ statusCode: 'ACTIVE', id: 'KSEA' }` | `statusCode` must be a number between 100 and 999 (inclusive) | | `{ body: {...}, fixture: 'f.json' }` | `body` and `fixture` cannot both be set, pick one | | `{ fixture: ['a.json'] }` | `fixture` must be a string containing a path and, optionally, an encoding | | `{ headers: { retries: 3 } }` | `headers` must be a map of strings to strings | A loud error is the good case. The quiet case — a `body` or `headers` key that happens to be type-valid — is the one to watch for. ## Confirming what was actually sent Because the wrong reading fails silently, verify rather than assume. Two checks take seconds: - Open the **Routes** panel in the Command Log. A route Cypress considered a stub is marked as stubbed, and its matched requests are drawn with an unfilled indicator. - Click the stubbed request. Cypress prints the request and the response it sent to the browser console, so the body the application actually received is right there — a bare string where you expected an object is unmistakable. If you are already asserting on what the component rendered, that assertion catches the case too, but it points at the component rather than at the route. Reading the response Cypress sent is the shorter path from symptom to cause. ## The same question in a handler The ambiguity is specific to the object form. Inside a route handler, `req.reply()` reads its arguments **positionally**, so `req.reply(payload)` sets the body and `req.reply(404, payload)` sets a status code and a body with no key-guessing at all. Passing a single object that happens to carry `StaticResponse` keys still describes a response, though, so the same defensive habit applies: when the payload is data, say `req.reply({ body: payload })` and there is nothing left to interpret.

  • Does the same key rule apply to `req.reply()` inside a Cypress route handler?
    `req.reply()` reads its arguments positionally rather than by key. `req.reply(body)` sets the body, `req.reply(body, headers)` adds headers, and `req.reply(statusCode, body, headers)` sets all three; a number in first position is always a status code. Passing a single object that carries `StaticResponse` keys is still treated as a `StaticResponse`, so the same defensive `{ body: ... }` habit helps.
  • What content type does Cypress send when the body is a plain object?
    When the body is an object rather than a string or `ArrayBuffer`, Cypress serialises it with `JSON.stringify` and adds `content-type: application/json` unless your `headers` already set a content type. That default is what makes `cy.intercept('/api/stations', [{ id: 'KSEA' }])` arrive as parsed JSON in the application without any extra configuration.

saying these in an interview costs you the question

  • Assumes any object argument becomes the response body
  • Never writes an explicit body wrapper
  • Blames the app when a stub returns a bare string
  • Expects a bad statusCode to be silently ignored