Resource library

QA How-To

Cypress cy.intercept: Examples

Use every cypress cy.intercept example you need for spying, stubbing, route matching, GraphQL, errors, retries, response control, caching, and typing.

21 min read | 2,453 words

TL;DR

A reliable cypress cy.intercept example defines a precise route before the request, applies an alias, triggers the action, and asserts request, response, and UI behavior. Choose spying, stubbing, or pass-through control based on the test boundary.

Key Takeaways

  • Register every intercept before the page or user action can send the matching request.
  • Use a narrow method and route matcher so an unrelated request cannot satisfy the alias.
  • Spy on real traffic for integration evidence and stub responses for deterministic frontend states.
  • Use req.reply for a controlled response and req.continue to inspect or modify the real response.
  • Alias GraphQL operations by operationName because many operations share one endpoint.
  • Account for reverse registration order, one-time routes, browser cache, and service workers during debugging.

A useful cypress cy.intercept example should register a precise route before the application sends the request, alias that route, trigger the user action, and assert the resulting request, response, and UI state. cy.intercept() can observe real traffic, return a static response, or modify the request-response cycle without changing application code.

This guide is a practical cookbook for current Cypress network testing in 2026. The examples cover URL matching, query parameters, fixtures, dynamic handlers, errors, latency, GraphQL, repeated calls, cache problems, and TypeScript typing. Each pattern includes the reason to use it, not just syntax.

TL;DR

it('creates a project', () => {
  cy.intercept('POST', '/api/projects').as('createProject')
  cy.visit('/projects/new')

  cy.get('input[name="name"]').type('Checkout reliability')
  cy.contains('button', 'Create project').click()

  cy.wait('@createProject').then(({ request, response }) => {
    expect(request.body).to.include({ name: 'Checkout reliability' })
    expect(response?.statusCode).to.eq(201)
  })

  cy.contains('Project created').should('be.visible')
})
Mode Handler Traffic reaches server? Best for
Spy No response handler Yes Synchronization and contract observation
Static stub StaticResponse No Deterministic UI states
Dynamic stub req.reply() Your choice Conditional responses
Pass-through modification req.continue() Yes Inspect or change real response
Network failure { forceNetworkError: true } No successful response Offline and retry behavior

1. Start with a Spy-Only cypress cy.intercept example

Spy-only interception observes a real request without replacing its response. Specify the HTTP method whenever it matters. If you omit it, the route matches every method for the URL pattern.

describe('project list', () => {
  it('waits for the real projects request', () => {
    cy.intercept('GET', '/api/projects').as('getProjects')

    cy.visit('/projects')

    cy.wait('@getProjects').then(({ request, response }) => {
      expect(request.method).to.eq('GET')
      expect(response?.statusCode).to.eq(200)
      expect(response?.body).to.have.property('items')
    })

    cy.get('[data-cy="project-list"]').should('be.visible')
  })
})

The intercept must exist before cy.visit() because the page can request projects immediately. cy.intercept() yields null, but .as('getProjects') creates a route alias. cy.wait('@getProjects') yields the matched interception with request and response details.

This pattern is stronger than a fixed wait because it synchronizes on an application event. It still depends on the environment and real API data, so keep assertions stable. Verify the response contract and visible behavior without assuming that a shared environment contains one particular customer's project.

Use spy-only interception for critical integrated paths, analytics calls, request payload checks, and places where the server behavior itself matters.

2. Match Exact URLs, Globs, Regex, and RouteMatcher Fields

Accurate route matching prevents false waits and accidental stubs. Cypress can match a string, glob, regular expression, or RouteMatcher object. String patterns are evaluated against the full request URL, and glob strings use Cypress.minimatch with matchBase: true.

// Exact path on the current host
cy.intercept('GET', '/api/projects')

// Glob across hosts and project IDs
cy.intercept('GET', '**/api/projects/*')

// Only numeric project IDs
cy.intercept('GET', /\/api\/projects\/\d+$/)

// Structured matching
cy.intercept({
  method: 'GET',
  pathname: '/api/search',
  query: { q: 'cypress', page: '1' },
}).as('searchProjects')

Every populated RouteMatcher property must match. Useful properties include method, url, hostname, path, pathname, query, headers, https, port, auth, times, and middleware.

Use pathname plus query when query order or URL encoding can vary. A literal question mark in a glob must be escaped in the JavaScript string, for example '/users\\?_limit=*'. Otherwise minimatch treats ? as a single-character wildcard.

Test a confusing glob directly:

expect(
  Cypress.minimatch(
    'https://app.example.test/api/projects/42',
    '**/api/projects/*',
  ),
).to.eq(true)

Prefer the narrowest readable matcher. A broad **/projects* can consume background calls and make aliases misleading.

3. Return a Static JSON Response

A static response makes a frontend scenario deterministic. Provide a status code, body, headers, or other StaticResponse properties.

it('renders one controlled project', () => {
  cy.intercept('GET', '/api/projects', {
    statusCode: 200,
    headers: { 'content-type': 'application/json' },
    body: {
      items: [
        { id: 42, name: 'Checkout reliability', status: 'active' },
      ],
      total: 1,
    },
  }).as('getProjects')

  cy.visit('/projects')
  cy.wait('@getProjects').its('response.statusCode').should('eq', 200)
  cy.contains('Checkout reliability').should('be.visible')
  cy.get('[data-cy="project-card"]').should('have.length', 1)
})

The request does not need a live backend response because Cypress answers it at the network layer. This is ideal for rendering, sorting, empty state, permission state, and client-side calculation tests.

Do not claim the test validates backend integration. It validates the browser against the supplied response. Keep direct API tests and a smaller number of non-stubbed end-to-end paths for server behavior.

Static stubs are automatically cleared before every test. Register shared baseline routes in beforeEach, then override them inside a test if needed. Normal intercept routes are matched in reverse definition order, while routes with { middleware: true } run first.

4. Serve Fixture Data through cy.intercept

Store a larger reusable payload under cypress/fixtures, then reference it with the fixture property:

it('renders an empty project list', () => {
  cy.intercept('GET', '/api/projects', {
    fixture: 'projects/empty-page.json',
  }).as('getProjects')

  cy.visit('/projects')
  cy.wait('@getProjects')
  cy.contains('No projects yet').should('be.visible')
})

Use fixture or body, not both. Load the fixture first if the scenario changes part of the response:

cy.fixture('projects/page-one.json').then((page) => {
  cy.intercept('GET', '/api/projects', {
    statusCode: 200,
    body: { ...page, total: 0, items: [] },
  }).as('getProjects')
})

Register the route inside .then() and place cy.visit() afterward in the Cypress queue. This guarantees the data has loaded and the intercept exists before the request.

Fixtures are best for stable, synthetic payloads. Do not commit production snapshots or secrets. The cypress cy.fixture tutorial explains caching, encoding, typing, and file-size choices, while the cypress cy.fixture examples provide more data-driven recipes.

5. Assert a POST Request and Real Response

Spying on a write request lets you verify what the UI sent and how the integrated server answered.

type CreateProjectRequest = {
  name: string
  ownerId: number
}

type CreateProjectResponse = {
  id: number
  name: string
  status: 'active'
}

it('sends the project payload', () => {
  cy.intercept<CreateProjectRequest, CreateProjectResponse>(
    'POST',
    '/api/projects',
  ).as('createProject')

  cy.visit('/projects/new')
  cy.get('input[name="name"]').type('Checkout reliability')
  cy.contains('button', 'Create project').click()

  cy.wait('@createProject').then(({ request, response }) => {
    expect(request.body.name).to.eq('Checkout reliability')
    expect(request.body.ownerId).to.be.a('number')
    expect(response?.statusCode).to.eq(201)
    expect(response?.body.status).to.eq('active')
  })

  cy.location('pathname').should('match', /\/projects\/\d+$/)
})

The generic parameters type request and response bodies inside the handler or interception. They do not validate runtime data, so direct contract tests should still check schemas where appropriate.

Assert fields that the UI owns. Avoid duplicating every server field in a browser test, especially generated timestamps and IDs. The visible URL and confirmation state prove that the browser handled success.

If the application sends JSON text rather than an object because of its client configuration, inspect the actual request.body before writing assertions. Network tests should reflect the real wire behavior.

6. Modify an Outgoing Request with a Route Handler

The route handler receives req and can change headers, query data, or the body before the request reaches the server. Use this carefully because it can make the test environment behave differently from production.

it('adds a controlled feature header for the scenario', () => {
  cy.intercept('GET', '/api/projects', (req) => {
    req.headers['x-test-feature'] = 'new-project-list'
  }).as('getProjects')

  cy.visit('/projects')
  cy.wait('@getProjects').its('request.headers')
    .should('include', { 'x-test-feature': 'new-project-list' })
})

Calling neither req.reply() nor req.continue() allows the request to continue after the handler finishes. Use req.reply() to end the request phase with a stub:

cy.intercept('POST', '/api/projects', (req) => {
  if (req.body.name === 'Duplicate project') {
    req.reply({
      statusCode: 409,
      body: { code: 'PROJECT_EXISTS', message: 'Project name already exists' },
    })
  }
}).as('createProject')

If the condition does not call req.reply(), the request can continue to other matching handlers or the server. Keep conditions explicit and add aliases that reveal which business call is being observed.

Do not place Cypress commands such as cy.fixture() inside a route handler. The handler executes during the network lifecycle and should use data prepared earlier. Load required fixture data before registering the route.

7. Inspect or Change a Real Response with req.continue

req.continue() sends the request to the destination and optionally gives you the real response before it reaches the browser. This supports response assertions, header removal, or a controlled response transformation.

it('removes caching from the project response', () => {
  cy.intercept('GET', '/api/projects', (req) => {
    req.continue((res) => {
      res.headers['cache-control'] = 'no-store'
      expect(res.statusCode).to.eq(200)
    })
  }).as('getProjects')

  cy.visit('/projects')
  cy.wait('@getProjects')
  cy.get('[data-cy="project-list"]').should('be.visible')
})

Inside a response callback, res.body, res.headers, res.statusCode, and res.statusMessage can be inspected or changed. Current APIs include res.setDelay(milliseconds), res.setThrottle(kilobitsPerSecond), and res.send() for ending the response phase.

Use modification only when it represents the scenario. If every test strips or rewrites fields, the suite can miss integration defects. A response assertion that does not mutate data is safer for critical paths.

Only one matching handler should call req.continue() for a request. Cypress sends that request to the server and the callback becomes part of the response phase. Name helper functions around the business reason rather than hiding broad traffic changes in global support code.

8. Test Errors, Offline Behavior, Delay, and Throttling

Client applications need explicit tests for HTTP errors and network failures. They are different conditions.

it('shows a permission message for 403', () => {
  cy.intercept('GET', '/api/projects', {
    statusCode: 403,
    body: { code: 'FORBIDDEN', message: 'Projects are not available' },
  }).as('getProjects')

  cy.visit('/projects')
  cy.wait('@getProjects')
  cy.contains('You do not have access to projects').should('be.visible')
})

it('shows retry UI after a network failure', () => {
  cy.intercept('GET', '/api/projects', { forceNetworkError: true })
    .as('getProjects')

  cy.visit('/projects')
  cy.wait('@getProjects').should('have.property', 'error')
  cy.contains('button', 'Try again').should('be.visible')
})

Add controlled delay through a static response:

cy.intercept('GET', '/api/projects', {
  statusCode: 200,
  body: { items: [], total: 0 },
  delay: 1200,
}).as('slowProjects')

Then assert that a loading indicator appears and disappears after the alias resolves. Use illustrative delays only to expose a state, not to benchmark performance. CI timing and intercept delay are not production performance measurements.

To fail only the first matching request, add times: 1 to a RouteMatcher, then let a later route handle retries. Register overlapping routes carefully because normal handlers are evaluated in reverse definition order.

9. Alias GraphQL Operations by Request Body

GraphQL often sends queries and mutations to one /graphql URL, so route URL alone cannot distinguish operations. Inspect req.body.operationName and assign a per-request alias.

beforeEach(() => {
  cy.intercept('POST', '/graphql', (req) => {
    if (req.body.operationName === 'ProjectList') {
      req.alias = 'gqlProjectList'
    }

    if (req.body.operationName === 'CreateProject') {
      req.alias = 'gqlCreateProject'
    }
  })
})

it('creates a project through GraphQL', () => {
  cy.visit('/projects/new')
  cy.get('input[name="name"]').type('Checkout reliability')
  cy.contains('button', 'Create project').click()

  cy.wait('@gqlCreateProject').then(({ request, response }) => {
    expect(request.body.variables.input.name).to.eq('Checkout reliability')
    expect(response?.body.errors).to.be.undefined
    expect(response?.body.data.createProject.id).to.exist
  })
})

Many GraphQL servers return HTTP 200 even when errors exists, so assert the response envelope rather than status alone. To stub one operation, call req.reply() only inside that operation condition and allow other operations to reach the server.

Persisted queries may omit a readable query string but still include an operation name or hash. Match the stable identifier your client actually sends. Avoid regex searches over formatted query text when operationName is available.

10. Handle Multiple Calls and Sequential Responses

Applications often poll, retry, or request the same route after an update. Cypress can expose all requests for an alias with the .all suffix and specific matches with 1-based numeric suffixes.

cy.intercept('GET', '/api/projects').as('getProjects')

cy.visit('/projects')
cy.reload()

cy.get('@getProjects.all').should('have.length', 2)
cy.get('@getProjects.1').its('response.statusCode').should('eq', 200)
cy.get('@getProjects.2').its('response.statusCode').should('eq', 200)

For a retry scenario, make the first request fail once and define the successful fallback first:

cy.intercept('GET', '/api/projects', {
  statusCode: 200,
  body: { items: [{ id: 42, name: 'Recovered project' }], total: 1 },
}).as('getProjects')

cy.intercept(
  { method: 'GET', url: '/api/projects', times: 1 },
  { forceNetworkError: true },
).as('firstProjectsFailure')

Normal routes use reverse definition order, so the one-time failure registered last matches first. After it is exhausted, the success route handles the retry.

Avoid mutable call counters when times expresses the need. If responses must vary across several calls, a route handler with an explicitly scoped counter can work, but reset it in beforeEach so tests remain isolated.

11. Diagnose an Intercept That Never Matches

The most common cause is timing: the request fired before registration. Move the intercept before cy.visit(), button click, or other trigger. Next, compare the actual method and full URL with the matcher. A missing leading slash, unexpected API host, or query pattern can prevent a match.

Check the Cypress Routes instrument. It lists registered matchers and match counts. Click the request entry to inspect request and response details. A yellow route badge shows that an intercept matched.

Browser caching is another cause. cy.intercept() operates at the network layer. If the browser serves a resource from cache, no network request occurs and the intercept cannot see it. Configure the test server to send cache-control: no-store, or use a top-level middleware intercept to remove response cache headers for the relevant API.

beforeEach(() => {
  cy.intercept(
    { url: 'https://api.example.test/**/*', middleware: true },
    (req) => {
      req.on('before:response', (res) => {
        res.headers['cache-control'] = 'no-store'
      })
    },
  )
})

Also verify service workers. A service worker can fulfill a request before it reaches the network path you expect. Use browser developer tools alongside the Cypress log and test the matcher with Cypress.minimatch when globs are involved.

Do not respond to a timeout by widening every route to **/*. That can make the test pass for the wrong request.

12. Create a Reusable cypress cy.intercept example Without Hiding Intent

Small helper functions can standardize response shape while keeping the route visible in the test.

type ProjectPage = {
  items: Array<{ id: number; name: string; status: string }>
  total: number
}

const stubProjects = (body: ProjectPage) => {
  return cy.intercept('GET', '/api/projects', {
    statusCode: 200,
    body,
  }).as('getProjects')
}

it('shows a paused project', () => {
  stubProjects({
    items: [{ id: 42, name: 'Migration', status: 'paused' }],
    total: 1,
  })

  cy.visit('/projects')
  cy.wait('@getProjects')
  cy.contains('[data-cy="project-card"]', 'Migration')
    .should('contain.text', 'Paused')
})

Return the Cypress chain from the helper, use a stable alias, and accept data that makes the state explicit. Avoid a global helper that stubs every API with hidden defaults. A reader should be able to see which service behavior the test controls.

Separate helpers for spy, success stub, and error stub can clarify intent. Keep route registration near the action that depends on it, and do not overwrite unrelated intercepts in a broad support hook.

For larger suites, document which tests use real services and which use stubs. That distinction helps reviewers interpret failures and prevents a fully mocked suite from being mistaken for end-to-end integration coverage.

13. Decide What to Spy, Stub, and Leave Alone

Network control should serve the scenario, not become a default that replaces every dependency. Start by identifying the behavior under test. If a checkout screen must render a rare decline code, stub that payment response. If the contract between the browser and the orders service is the risk, spy on the real request. If an unrelated telemetry call has no effect on the scenario, leave it alone or disable it through the application's test configuration.

Test purpose Network choice Main assertion
Request contract Spy Method, body, headers, response status
Deterministic frontend state Static or dynamic stub Visible state and user action
Retry handling Sequential stubs Attempts, recovery, final message
Integrated critical path Spy or no intercept Real service outcome
Third-party instability Controlled stub at owned boundary Application fallback behavior

Do not intercept assets and background APIs merely to make the Command Log quiet. Every route adds maintenance and can change request behavior. Use { log: false } on a StaticResponse only for genuinely noisy, understood traffic, and keep business routes visible during debugging.

Reset is automatic before each test, but test design still matters. A route registered in one test does not carry into the next. Put mandatory baseline routes in beforeEach and scenario overrides in the test body. Confirm the scenario route has matched, especially when a broad baseline could otherwise answer the request.

Finally, make aliases read like events: @loadProject, @saveSettings, or @createInvoice. Names such as @api1 make failures harder to understand. Network assertions should reveal product intent at the same speed as the UI assertions around them.

Interview Questions and Answers

Q: What does cy.intercept yield?

cy.intercept() itself yields null, but it can be aliased. Waiting on the alias with cy.wait() yields an interception containing request, response, and sometimes error information.

Q: What happens if the HTTP method is omitted?

The route can match requests using any method. Specify GET, POST, or the expected method to prevent an unrelated call to the same URL from matching.

Q: What is the difference between req.reply and req.continue?

req.reply() supplies a stub response and ends the request phase. req.continue() sends the request to the real destination and can inspect or modify the real response in its callback.

Q: Why should intercept registration happen before cy.visit?

The application can send network requests during startup. If registration happens later, Cypress misses the call and a subsequent alias wait times out.

Q: How do you identify GraphQL operations sharing one endpoint?

Inspect req.body.operationName in a route handler and assign req.alias for the matching operation. Then wait on that operation-specific alias and assert variables plus the GraphQL response envelope.

Q: Why might an intercept not see a request shown by the page?

The resource may have been served from browser cache or a service worker, so no normal network request reached Cypress's intercept layer. Check developer tools, response cache headers, matcher details, and registration timing.

Q: How do you make only the first request fail?

Use a RouteMatcher with times: 1 for the failure and a separate route for the successful retry. Account for reverse registration order among normal intercept routes.

Common Mistakes

  • Registering the intercept after the page or action already sent the request.
  • Omitting the method and matching an unrelated call to the same URL.
  • Using an overly broad glob such as **/* to silence a timeout.
  • Treating a stubbed UI test as proof that the real backend contract works.
  • Calling Cypress commands from inside a route handler.
  • Forgetting that normal overlapping routes match in reverse definition order.
  • Asserting only HTTP status for GraphQL responses that can contain errors with status 200.
  • Adding fixed waits instead of waiting on a named request alias.
  • Ignoring browser cache when an intercept match count stays at zero.
  • Mutating every real response globally and masking integration behavior.

Conclusion

A dependable cypress cy.intercept example starts with a narrow matcher, registers before the trigger, names the route, and verifies the right boundary. Spy when server integration matters, stub when a deterministic frontend state matters, and use route handlers only when conditional control is required.

Build confidence by combining request and response assertions with visible UI outcomes. Review the Routes instrument when a match fails, account for cache and route order, and keep the difference between mocked coverage and integrated coverage explicit.

Interview Questions and Answers

What are the main cy.intercept modes?

It can spy on real traffic, return a static stub, or use a route handler for conditional control. I choose spy mode when integration matters and stubbing when deterministic client behavior is the focus. Dynamic handlers are reserved for cases static data cannot express clearly.

Why should the HTTP method be specified?

Without a method, the matcher can accept every method for that URL. A POST might satisfy an alias intended for GET and make the test pass for the wrong event. A narrow method makes the network contract explicit.

Explain req.reply versus req.continue.

`req.reply()` supplies a stub response and ends the request phase. `req.continue()` sends the request to the destination and optionally exposes the real response for inspection or modification. I avoid response mutation when a critical integration path should remain realistic.

How would you test a retry after a network error?

I define a one-time failure with a `RouteMatcher` containing `times: 1` and a separate success route. I account for reverse registration order, trigger the request, assert the retry UI, and verify the eventual successful state.

How do you debug cy.wait timing out on an alias?

I verify registration occurred before the trigger, compare the actual method and full URL, and inspect the Routes instrument. I test glob patterns with `Cypress.minimatch` and check whether browser cache or a service worker prevented a network request.

How do you intercept multiple GraphQL operations?

I use one handler on the GraphQL endpoint, branch on `operationName`, and assign operation-specific `req.alias` values. This prevents a query from satisfying a wait intended for a mutation and makes assertions readable.

Does a stubbed cy.intercept test prove backend integration?

No. It proves the frontend handles the response supplied by the test. I combine those deterministic UI scenarios with direct API contracts and a smaller set of real end-to-end paths for backend confidence.

Frequently Asked Questions

What does cy.intercept do in Cypress?

It observes or controls browser network requests that match a route. You can spy on real traffic, return a static response, or use a handler to modify the request and response lifecycle.

Does cy.intercept wait for a request?

`cy.intercept()` registers a route and yields `null`. Alias the route with `.as()` and call `cy.wait('@alias')` after triggering the request to wait for and inspect a matching cycle.

How do I stub JSON with cy.intercept?

Pass a `StaticResponse` such as `{ statusCode: 200, body: { items: [] } }`. For a stored payload, use `{ fixture: 'path/file.json' }` instead of `body`.

Why is my Cypress intercept not matching?

The request may have fired before registration, the method or URL may differ, or the browser may have served it from cache. Inspect the Routes instrument, full request URL, query matching, service workers, and response cache headers.

How do I intercept GraphQL requests?

Intercept the shared GraphQL endpoint, inspect `req.body.operationName`, and assign `req.alias` for the operation you need. Assert both variables and the GraphQL `data` or `errors` envelope.

What is the difference between forceNetworkError and a 500 response?

`forceNetworkError` destroys the connection and models a network-level failure. A 500 response is a completed HTTP exchange with an error status and optional response body.

Are Cypress intercepts cleared between tests?

Yes, Cypress automatically clears registered intercepts before each test. Put required baseline routes in `beforeEach` and define scenario-specific overrides in the individual test.

Related Guides