skip to content

Network Stubbing and Intercept

How cy.intercept picks the requests a route applies to and what it then does with each one: spy, stub, rewrite, delay or break it. It is how an end-to-end suite stops depending on a real backend.

on this pageshow

explore

questions

25

In Cypress, what does `cy.wait('@getForecast')` yield to the next command?

level: juniorimportance: must knowfreq 78%

answer

  1. What comes back once the route resolves
  2. One object per request-response cycle
  3. Two halves: what went out, what returned
  4. Also carries id, routeId and error
  5. Chain .its('response.statusCode') to assert

basics

~20 s

cy.wait on an intercept alias yields one interception object for that request-response cycle: request and response, plus id, routeId, and an error property when the request failed. Chain .its() or .then() to assert on it.

solid answer

~40 s

`cy.wait('@getForecast')` resolves once a request matched by the aliased `cy.intercept()` route has completed, and it yields an **interception object** describing that one cycle. The properties you use are `request` (`method`, `url`, `headers`, `body`) and `response` (`statusCode`, `headers`, `body`), alongside `id` and `routeId`; a request that ended in a network error yields `error` and no `response`. Cypress parses `response.body` into an object only when the response carries a `Content-Type: application/json` header — otherwise it stays a string. From there you assert as you would on any subject: `cy.wait('@getForecast').its('response.statusCode').should('eq', 200)`, or `.then(({ request, response }) => { … })` for anything that needs several properties at once. The millisecond form, `cy.wait(ms)`, is different — it yields back whatever subject the chain already had.

code

javascript · 15 lines
javascript
describe('forecast panel', () => {
  it('renders what the forecast API returned', () => {
    cy.intercept('GET', '/api/forecast').as('getForecast')

    cy.visit('/dashboard')

    cy.wait('@getForecast').then(({ request, response }) => {
      expect(request.method).to.equal('GET')
      expect(response.statusCode).to.equal(200)
      expect(response.body.periods).to.have.length(7)
    })

    cy.get('[data-testid="forecast-day"]').should('have.length', 7)
  })
})

go deeper

for a junior

Be ready to name the two halves of what comes back — request and response — and to show one assertion off each, such as .its('response.statusCode').should('eq', 200).

for a middle

Explain why .as() names a route while cy.wait consumes a request, and why response.body is sometimes a string: Cypress parses it only for a JSON content type.

for a senior

Show that you read the interception when a failure is ambiguous, using status, url and headers to say whether the app asked wrongly or the server answered wrongly.

for a principal

Be ready to say how much of a suite should assert on interception internals at all, and what a team convention for that looks like across many specs.

## Two commands, one contract `cy.intercept()` registers a **route**: it tells Cypress which requests to watch and, optionally, what to reply with. The command yields nothing itself, which is why you chain `.as()` onto it — `.as()` names the *route*. `cy.wait('@getForecast')` then blocks the command queue until a request that route matched has completed, and hands the next command a single **interception object** describing that one request-response cycle. The split matters. `.as()` names a route, not a request; `cy.wait()` consumes one request the route matched. Nothing about the alias is a readable value until a request has actually gone through it. ## What is on the interception object | Property | Present when | What it holds | |---|---|---| | `request` | always | The outgoing request: `method`, `url`, `headers`, `body` | | `response` | the server or the stub answered | `statusCode`, `headers`, `body` | | `error` | the request failed at the network level | The captured error; `response` is absent | | `id` | always | Cypress's identifier for this one request-response cycle | | `routeId` | always | The identifier of the `cy.intercept()` route that matched it | A few details about the payload itself are worth carrying into an interview: - `response.body` is parsed into an object **only** when the response carries a `Content-Type: application/json` header. A plain-text body, a `301` redirect or a `304 Not Modified` gives you `response.body` as a **string**. - `request.url` is the fully-qualified URL the browser actually asked for, query string included — not the pattern you wrote in `cy.intercept()`. - The object is a settled snapshot. By the time `cy.wait()` releases the queue, the cycle it describes is finished, so nothing on it changes afterwards. ## Asserting on what came back The interception is an ordinary JavaScript object, so you assert on it the way you assert on any Cypress subject: 1. `.its('response.statusCode').should('eq', 200)` — read one nested value and assert on it. `.its()` is a query, so it starts a fresh chain. 2. `.then((interception) => { … })` — take the whole object and use Chai's `expect` inside the callback. This is what you want when several properties have to be checked together, or when a value has to be derived from the body. 3. `.should('have.property', 'error')` — assert on the interception itself. Useful for the failure case, where there is no `response` to read at all. Destructuring reads well inside `.then()`, because the two properties you almost always want are the top-level ones: `.then(({ request, response }) => …)`. ## The millisecond form yields something else entirely `cy.wait()` has a second signature that takes a number of milliseconds. Given a number, it yields back **the same subject the chain already had** — there is no interception, because no route was involved. That is precisely why the alias form is the one you can chain `.its('response.statusCode')` onto: only the alias form produces a network-shaped subject. ## Where it goes wrong - **Waiting on an alias that is not a route.** A DOM alias or a spy alias fails with `cy.wait() only accepts aliases for routes. The alias: 'x' did not match a route.` - **Treating the yielded object as the body.** `cy.wait('@getForecast').should('have.length', 7)` does not check the forecast array; it checks the interception object, which has no length. - **Expecting an array from one alias.** A single alias yields a single interception. An array comes from passing `cy.wait()` an array of aliases, or from `cy.get('@getForecast.all')`. - **Reading `response` on a failed request.** When the request errored, `response` is `undefined` and `error` is set, so `.its('response.statusCode')` fails with an unhelpful "cannot read property of undefined" instead of surfacing the network error you actually hit. ## In practice A test for a weather dashboard usually wants two things out of one wait: confirmation that the app asked for the right resource, and a look at what came back. ```javascript cy.intercept('GET', '/api/forecast').as('getForecast') cy.visit('/dashboard') cy.wait('@getForecast').then(({ request, response }) => { expect(request.method).to.equal('GET') expect(response.statusCode).to.equal(200) }) ``` The interception is the half of the picture the rendered page cannot show you: it tells you exactly what crossed the wire, with the status and headers attached, which is why a failure here points at a specific request rather than at a missing element.

  • When does `interception.response.body` come back as a string rather than an object?
    Cypress parses the body only when the response carries a `Content-Type: application/json` header. Anything else — a plain-text payload, a `301` redirect, a `304 Not Modified` — yields `response.body` as a string, so `.its('response.body.periods')` will not resolve. Assert on the string, or parse it yourself inside `.then()`.
  • What does `cy.wait('@getForecast')` yield when the request never received a response?
    The interception still comes back, but with an `error` property set and no `response`. That is why Cypress documents `cy.wait('@alias').should('have.property', 'error')` as the way to assert a route ended in a network failure rather than in a status code.

saying these in an interview costs you the question

  • Says cy.wait yields the response body directly
  • Expects an array of interceptions from one alias
  • Thinks cy.wait('@alias') yields a DOM element
  • Assumes response.body is always a parsed object
  • Waits on an alias never attached to cy.intercept
open as a page

What are the three ways to call Cypress's `cy.intercept()`, and what does each match?

level: juniorimportance: must knowfreq 78%

basics

~10 s

Cypress accepts cy.intercept(url), cy.intercept(method, url) and cy.intercept(routeMatcher). A bare url matches every HTTP method; the method form narrows it to one; the routeMatcher object matches fields such as pathname, query and headers together.

open as a page

In Cypress, what turns a `cy.intercept()` route from a spy into a stub?

level: juniorimportance: must knowfreq 80%

basics

~20 s

The response argument. A Cypress cy.intercept('GET', '/api/forecast') with no second argument only spies, and the request still reaches the real server. Supply a StaticResponse, a plain body, or a handler that calls req.reply() and Cypress answers it instead.

open as a page

In Cypress, how does a `cy.intercept()` 500 stub differ from `forceNetworkError`?

level: juniorimportance: must knowfreq 70%

basics

~20 s

A 500 stub sends a real HTTP response the application can read, so its status and error body are available. forceNetworkError destroys the browser connection instead: no status, no headers, no body, and the app's fetch rejects.

open as a page

In Cypress 16, why is `req.httpVersion` undefined inside a `cy.intercept()` handler in Chrome?

level: middleimportance: must knowfreq 62%

basics

~20 s

Because Chrome, Chromium and Edge now intercept on the native browser network. The browser negotiates the protocol with your server directly, and that is not known when the request is paused, so Cypress reports no value rather than a wrong one.

open as a page

Cypress `cy.intercept()` cannot stub WebSocket frames, so how do you test a socket-fed UI?

level: middleimportance: must knowfreq 58%

basics

~20 s

You move the seam. Cypress documents socket traffic as working but not interceptable, so drive messages from somewhere you control: stub the app's own message handler, have the server push on cue, or run a helper client outside the browser.

open as a page

A Cypress wait fails: 'timed out waiting 5000ms for the 1st request to the route'. What does that mean?

level: seniorimportance: must knowfreq 55%

basics

~20 s

The request phase timed out: no request matching that aliased route left the browser inside requestTimeout, which defaults to 5000 ms. The app never made the call, so widening responseTimeout changes nothing about this failure.

open as a page

In Cypress, which of several overlapping `cy.intercept()` routes handles a request first?

level: seniorimportance: must knowfreq 52%

basics

~20 s

Cypress consults matching routes in reverse order of definition, so the most recently registered one goes first. Routes declared with middleware set to true are the exception: they always run first, in the order they were defined.

open as a page

Why can a Cypress `cy.intercept()` route never fire even though the page loads that resource?

level: juniorimportance: should knowfreq 48%

basics

~20 s

Because the browser answered from its own HTTP cache. cy.intercept() matches at the network layer, so a request that is never issued never reaches Cypress, the route stays at zero matches, and a wait on its alias fails.

open as a page

In Cypress, what does `cy.wait(['@getStations', '@getAlerts'])` resolve on and yield?

level: middleimportance: should knowfreq 42%

basics

~20 s

It blocks until every listed route alias has completed a request-response cycle, each within the usual requestTimeout and responseTimeout budgets, then yields an array of interceptions ordered by the aliases as you passed them, not by arrival.

open as a page

In Cypress, what does a second `cy.wait('@getForecast')` on the same alias wait for?

level: middleimportance: should knowfreq 52%

basics

~20 s

The second matching request, not the first one again. Cypress keeps a per-alias counter for the test, and each cy.wait on that alias claims the next recorded request in order, so a third call waits for the third.

open as a page

In a Cypress `cy.intercept()` URL glob, what do `*`, `**` and `?` mean?

level: middleimportance: should knowfreq 55%

basics

~20 s

Cypress glob-matches URL strings with minimatch: one star matches within a single path segment, two stars match across segments, and a question mark is a single-character wildcard, so a literal query-string question mark must be written as an escaped backslash-question in JavaScript.

open as a page

How do a Cypress `cy.intercept()` routeMatcher's `url`, `path` and `pathname` differ?

level: middleimportance: should knowfreq 48%

basics

~10 s

In a Cypress routeMatcher, url is the full request URL including protocol and host, path is everything after the hostname with the query string, and pathname is that path without the query string.

open as a page

In a Cypress `cy.intercept()` handler, how do you build the reply from the request?

level: middleimportance: should knowfreq 58%

basics

~20 s

Pass 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.

open as a page

In a Cypress `cy.intercept()` reply, how do `delay` and `throttleKbps` differ?

level: middleimportance: should knowfreq 52%

basics

~20 s

delay holds the whole response back for a number of milliseconds and then sends it at full speed. throttleKbps leaves the wait unchanged but streams the body slowly. One models latency, the other models bandwidth.

open as a page

In Cypress 16, how does Chrome's own HTTP cache change what a `cy.intercept()` observes?

level: seniorimportance: should knowfreq 38%

basics

~20 s

The cache now sits between your server and Cypress. A revalidation your server answers with 304 is merged with the cached copy and reported to the intercept as a complete 200, and a response Cypress produced itself is never cached at all.

open as a page

In a Cypress `req.continue()` handler, why is `res.body.alerts` undefined?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Because res.body is a string, not an object. Cypress parses a response body as JSON only when the response carries a Content-Type of application/json; without that header it yields the raw text, so property access returns undefined. Parse it yourself first.

open as a page

A Cypress `cy.intercept()` route with `forceNetworkError` fails the spec with the app's own error. Why?

level: seniorimportance: should knowfreq 44%

basics

~20 s

The injected fault is a real failure, so the application's request genuinely rejects. If nothing in the app catches that rejection it reaches the window, and Cypress fails a test on any uncaught exception from the application under test.

open as a page

In a Cypress suite, how much logic belongs inside a `cy.intercept()` route handler?

level: principalimportance: should knowfreq 26%

basics

~20 s

Enough to compute a reply from what the request already carries, and no more. A Cypress handler that encodes validation, pagination or business rules becomes an unversioned second implementation of the service, hidden in a spec that nothing else tests.

open as a page

How far should a Cypress suite go in throttling every response with `res.setThrottle()`?

level: principalimportance: should knowfreq 30%

basics

~20 s

Keep injected slowness out of the default path. A support-file route that throttles every response buys uniform timing pressure but charges wall-clock time to every spec forever, so put delay on the routes a specific test is about instead.

open as a page

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

level: middleimportance: nice to knowfreq 32%

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.

open as a page

Why does Cypress reject a `cy.intercept()` reply setting both `statusCode` and `forceNetworkError`?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

Because a destroyed connection has nothing to carry a status line, headers or a body. Cypress validates the StaticResponse when the route is declared and throws: forceNetworkError, if passed, must be the only option in the StaticResponse.

open as a page

In Cypress, a background poll keeps satisfying your `cy.wait('@getForecast')` calls. Why, and what do you change?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Because a route alias records every request the route matches, whoever caused it. Each cy.wait consumes the next recorded request, so a timer's refresh takes a slot and your second wait resolves against it rather than against the fetch you triggered.

open as a page

In Cypress, how do you make a `cy.intercept()` route match only one of two same-path requests?

level: seniorimportance: nice to knowfreq 33%

basics

~10 s

Add more routeMatcher properties, because Cypress ANDs everything you set. A query or headers dictionary, or the method, hostname, port and https fields, separates two calls that share a path.

open as a page

Your Cypress 16 suite passes only with `forceHttp1: true`. Is that an acceptable place to stop?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Only as a dated migration step. Cypress introduced and deprecated forceHttp1 in the same release, so leaving it on trades a green suite for a run that no longer tests the protocol, the certificate or the cache your users get.

open as a page