In Cypress, which of several overlapping `cy.intercept()` routes handles a request first?
answer
- Definition order matters, but not the way you expect
- One matcher option jumps the queue
- A route with no handler does not stop anything
- Intercepts do not survive into the next test
- One option limits how often a route matches
basics
~20 sCypress 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.
solid answer
~40 sCypress matches `cy.intercept()` routes in **reverse order of definition** — the last one registered is consulted first — except for routes declared with `{ middleware: true }`, which are pulled to the front and run in definition order. Walking that list, the first route that supplies a response ends the request; a route registered with no handler at all is a spy and lets control fall through to the next match. Since all intercepts are cleared before every test, a route registered inside a test is later than one registered in `beforeEach` and therefore wins, which is how a spec overrides a suite-wide stub without touching it. `times: n` caps how many requests a route may match; once used up the route is disabled and the next matching route takes over.
code
javascript · 13 linesbeforeEach(() => {
cy.intercept('GET', '/api/alerts*', { statusCode: 200, body: [] })
})
it('shows a severe weather banner', () => {
cy.intercept('GET', '/api/alerts*', {
statusCode: 200,
body: [{ id: 1, severity: 'severe' }],
})
cy.visit('/dashboard')
cy.contains('Severe weather').should('be.visible')
})go deeper
Recall that several intercepts can match one request, and that adding a narrower intercept inside a test is the normal way to change what a shared stub does.
Explain the reverse-definition ordering, the middleware exception, and why a route with no handler lets the request keep travelling down the list.
Be ready to diagnose an override that will not take effect: registration order across hooks, a spy that is not actually shadowing anything, a middleware route, or a times allowance already spent.
Own the convention for where shared stubs live across a suite, since the ordering rule turns hook placement into a contract that every spec author depends on.
More than one `cy.intercept()` route can match the same request, and Cypress has a definite, documented order for consulting them. Knowing it is what lets a spec override a broad stub set up in a `beforeEach` without deleting or rewriting it. ## The ordering rule Routes are consulted in **reverse order of definition** — the most recently registered matching route goes first — with one exception: routes registered with `{ middleware: true }` are pulled to the front and run in the order they were **defined**. So the full order is: 1. Every matching `middleware: true` route, in definition order. 2. Every other matching route, in reverse definition order. Cypress then walks that list for the request phase: - If a route supplied a response, the request ends there and the remaining routes are not consulted. - If a route supplied a request-handler function, that function runs and may pass control on. - If a route supplied **nothing at all**, it is a spy: it records the request and control moves to the next matching route. Because all intercepts are cleared before every test, a `beforeEach` route is registered first each test and any route registered inside the test body is registered later — and therefore consulted first. That is the whole mechanism behind "the specific case overrides the general one". ## A weather dashboard override ```js beforeEach(() => { cy.intercept('GET', '/api/alerts*', { statusCode: 200, body: [] }) }) it('shows a severe weather banner', () => { // registered later, so consulted first cy.intercept('GET', '/api/alerts*', { statusCode: 200, body: [{ id: 1, severity: 'severe' }], }) cy.visit('/dashboard') cy.contains('Severe weather').should('be.visible') }) ``` Nothing removes the `beforeEach` route. It is still in the routing table and still matches — it is simply never reached, because the later route answers first. ## What middleware is for `middleware: true` inverts both halves of the rule: the route runs **before** the ordinary handlers, and middleware routes among themselves run in the order written. It exists for cross-cutting work that must happen on every matching request regardless of which specific route eventually answers — stamping a header onto every forecast call, or recording every request the dashboard makes. Two rules go with it: - A middleware route **must** be given a request-handler function. Passing a static response throws: *"cy.intercept()'s `handler` argument must be an HttpController function when `middleware` is set to `true`."* - Because it is layered on top rather than instead of, a middleware route normally modifies the request and lets it continue, rather than answering it. ## Bounding a route with times `times: n` caps how many requests a route may match. Once the route has matched `n` requests it is disabled, and the next matching request falls through to whatever route is next in the order — or, if there is none, straight to the server. - `times` must be a **positive integer**; anything else is rejected when the command runs. - It combines with the ordering rule to express "the first call behaves differently": register the bounded route last so it goes first, and let the broader route underneath absorb everything after it. - It is a matching constraint, not a retry or a timeout — the route stops being a candidate, it does not fail. ## Diagnosing an override that will not take When a route seems to be ignored, work through this list before suspecting the pattern: 1. Is the overriding route registered **after** the one it should beat? A route added in a `before` block loses to one added in `beforeEach`. 2. Does the earlier route actually answer? A spy-only route does not stop the chain, so it is not the one shadowing yours. 3. Is one of the two a `middleware: true` route? Those always run first, no matter when they were written. 4. Has a `times`-bounded route already used up its allowance in this test? 5. Do both patterns really match the request, or is the narrower one simply not matching at all? The Routes panel in the Cypress Command Log lists every registered route with its matcher and the number of requests it has matched, which usually settles the question in one glance.
- What does `{ middleware: true }` change about a Cypress `cy.intercept()` route, and what must its handler be?It moves the route ahead of every non-middleware route and makes middleware routes run in definition order rather than reversed, so it is the place for cross-cutting work on every matching request. Its handler must be a request-handler function; passing a static response throws, because middleware is meant to layer onto a request rather than answer it.
- In Cypress, what happens to a request after a `cy.intercept()` route with `times: 2` has already matched twice?The route is disabled once its allowance is used up, so it is no longer a candidate. The next request falls through to the next matching route in the order, or, if no other route matches, straight to the destination server. `times` must be a positive integer, and it constrains matching only — it is not a retry count or a timeout.
saying these in an interview costs you the question
- Thinks the first registered route always wins
- Believes a later route replaces or removes an earlier one
- Assumes middleware routes also run in reverse order
- Expects a spy-only route to stop later routes matching
- Treats times as a retry count rather than a match limit