Why does Cypress reject a `cy.intercept()` reply setting both `statusCode` and `forceNetworkError`?
answer
- Two options describing incompatible outcomes
- A destroyed connection carries nothing
- The check runs when the route is declared
- Only one option may be present
- Two routes when you need both failures
basics
~20 sBecause 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.
solid answer
~40 sThe two describe incompatible outcomes. `forceNetworkError: true` means Cypress never produces a response — it destroys the browser connection — so there is no message for a `statusCode`, `headers` or `body` to travel in. Rather than silently discarding whichever field it cannot honour, Cypress validates the StaticResponse at declaration time and throws: *An invalid StaticResponse was supplied to `cy.intercept()`. `forceNetworkError`, if passed, must be the only option in the StaticResponse.* The failure surfaces on the `cy.intercept()` line itself, not on the request that would have matched, which makes it cheap to fix. If a test needs both an unreachable host and a 503 from the same URL, that is two routes, and usually a `times` bound on the first.
go deeper
Recognise the message when you hit it, and know the fix is to remove the other fields rather than to reorder them or wrap the object differently.
Explain the reason from the mechanism: a destroyed connection produces no HTTP message, so there is nothing for a status line or headers to travel in.
Be able to restructure the test — two routes with a bound on the fault route — when a scenario genuinely needs a transport failure followed by a service error.
Weigh how much of a suite should depend on precisely sequenced fault routes at all, given how much harder ordered stubs are to read and to keep working.
This is one of the small validation rules that turns an incoherent test into an immediate, readable failure — and it is worth understanding because the reason is the mechanism, not a style preference. ## What Cypress actually says Declaring a route with both options throws as the command runs, before any request is made: ```js // throws immediately cy.intercept('GET', '/api/alerts*', { statusCode: 503, forceNetworkError: true, }) ``` > An invalid StaticResponse was supplied to `cy.intercept()`. `forceNetworkError`, if passed, must be the only option in the StaticResponse. The message is followed by the object you passed, so the offending field is visible in the failure itself. Note where the error lands: on the `cy.intercept()` command, not on the request that would have matched it later. A malformed route is a test-authoring bug, and Cypress reports it as one. ## Why the combination is incoherent A **StaticResponse** is a description of an HTTP reply. `statusCode`, `headers`, `body` and `fixture` all describe parts of a message. `forceNetworkError` says there will be no message: Cypress destroys the browser connection so the application's request fails at the transport layer, the way it would if the host were unreachable. There is no HTTP frame in that outcome for a status line to sit in. If Cypress accepted the object, it would have to quietly drop `statusCode` — and a test that silently ignores half of what you wrote is worse than one that refuses to run. The same reasoning covers `headers` and `body`. ## The other StaticResponse rules it sits alongside Cypress checks the whole object at declaration time. Useful ones to know: - `body` and `fixture` cannot both be set — pick one source for the reply body. - `fixture` must be a string, optionally `path,encoding`. - `statusCode` must be a number between 100 and 999. - `headers` must be a flat map of strings to strings. - `throttleKbps` must be a finite, non-negative number. - `delay` must be a finite, non-negative number, and below roughly 24.8 days — a larger value would be silently truncated by the underlying timer, so Cypress rejects it instead. ## The same check runs wherever the object is supplied A StaticResponse can reach Cypress in three places: as the handler argument to `cy.intercept()`, as the argument to `req.reply()` in a request handler, and as the argument to `res.send()` in a response handler. The validator runs in all three, and the message names the call that supplied the object, so a malformed reply built inside a handler fails on that line with the offending object printed underneath rather than producing a confusing response later. That distinction matters for handlers that build a reply conditionally. A branch that bolts `forceNetworkError` onto an object already carrying a `statusCode` throws only when that branch is taken — which may be several tests into a run, on a machine that is not yours. ## Staging both failures in one test When a scenario genuinely needs an unreachable host *and* a service error from the same URL — the dashboard drops the connection, retries, and then gets a 503 — you need two routes rather than one merged object. Two mechanics carry it: Cypress matches routes in **reverse order of declaration**, so the last declaration is tried first, and a route matcher can carry `times` to bound how many requests a route will claim. ```js // the fallback: any forecast request gets a service error cy.intercept('GET', '/api/forecast*', { statusCode: 503, body: { error: 'forecast service unavailable' }, }) // declared last, so it is tried first — and only once cy.intercept({ method: 'GET', url: '/api/forecast*', times: 1 }, { forceNetworkError: true, }) ``` Both halves matter. The order puts the fault ahead of the fallback, and `times: 1` retires the fault route after one request so everything afterwards falls through to the 503. Without the bound, a fault-injecting route is not a one-shot: it stays registered until the test ends and every matching request gets the same destroyed connection. ## What this rule protects you from The mistake it catches is usually a mental one — writing `statusCode: 0` or `statusCode: 503` next to `forceNetworkError` because it feels like the failure needs a label. It does not. A transport failure is identified in Cypress by the interception's `error` property, whose message is `forceNetworkError called`, and by the request's failed state in the Command Log. The application, meanwhile, identifies it by its request rejecting. Neither of them wants a status code, and neither of them would ever have seen the one you tried to attach.
- When does this error surface — at declaration or when the request is made?At declaration. Cypress validates the StaticResponse as the `cy.intercept()` command runs, so the test fails on that line with the object it received printed underneath. The same validation runs for an object passed to `req.reply()` or `res.send()`, failing at the point of the call rather than later in the response.
- How do you stage an unreachable host and then a 503 on the same URL?Declare two routes, and mind the order. Cypress matches routes in reverse order of declaration, so declare the 503 route first and the `forceNetworkError` route last, with `times: 1` in its route matcher. The fault route is tried first and retires after one request, and every later request falls through to the 503.
saying these in an interview costs you the question
- Adds statusCode 0 alongside forceNetworkError to 'label' the failure
- Expects Cypress to ignore the extra field silently
- Thinks the error appears when the request is made
- Believes one fault route stops matching after the first request
- Sets both body and fixture on the same StaticResponse