What Is cy.intercept? Spying, Stubbing and Modifying Network Requests

Every modern web application talks to APIs. Product data, user profiles, order histories, search results — all fetched via HTTP requests from the browser. cy.intercept() gives you control over every one of these requests. You can spy on them (watch without changing), stub them (replace with fake responses), or modify them (alter headers, inject delays, change payloads). This control is what makes Cypress uniquely powerful for testing how your application handles the full spectrum of network conditions — from instant success to catastrophic failure.

Three Modes of cy.intercept — Spy, Stub and Modify

Each mode serves a different testing purpose. Understanding when to use which mode is the key to effective network-level testing.

// ── MODE 1: SPY — watch requests without changing anything ──
// Use when: you want to verify the app makes the correct API calls

cy.intercept('GET', '/api/products').as('getProducts');
cy.visit('/shop');

// Wait for the real request to complete, then inspect it
cy.wait('@getProducts').then((interception) => {
  // Verify the request was made correctly
  expect(interception.request.url).to.include('/api/products');
  expect(interception.request.headers).to.have.property('authorization');

  // Verify the real response
  expect(interception.response.statusCode).to.eq(200);
  expect(interception.response.body).to.have.length(6);
});


// ── MODE 2: STUB — replace the response with fake data ──
// Use when: you need fast, deterministic tests independent of the backend

cy.intercept('GET', '/api/products', {
  statusCode: 200,
  body: [
    { id: 1, name: 'Stubbed Product', price: 9.99 },
    { id: 2, name: 'Another Product', price: 19.99 },
  ],
}).as('stubbedProducts');

cy.visit('/shop');
cy.wait('@stubbedProducts');

// The UI shows YOUR data, not the real API data
cy.get('.product-card').should('have.length', 2);
cy.contains('Stubbed Product').should('be.visible');


// ── MODE 3: MODIFY — alter the real request or response ──
// Use when: you need mostly-real behaviour with specific modifications

cy.intercept('GET', '/api/products', (req) => {
  req.continue((res) => {
    // Modify the real response before it reaches the browser
    res.body[0].price = 0.01;  // Change first product's price
    res.body[0].name = 'Modified by Cypress';
  });
}).as('modifiedProducts');


// ── Comparison table ──
const MODES = {
  'Spy': {
    changes_response: false,
    speed: 'Same as real API (network latency applies)',
    determinism: 'Depends on backend state and data',
    use_when: [
      'Verifying the app sends correct request payloads',
      'Asserting on real API response data',
      'Waiting for specific API calls to complete before UI assertions',
      'Integration testing where real backend behaviour matters',
    ],
  },
  'Stub': {
    changes_response: true,
    speed: 'Instant — no network round-trip',
    determinism: 'Fully deterministic — you control the data',
    use_when: [
      'Tests need consistent data regardless of backend state',
      'Testing error scenarios (500, 404, timeout)',
      'Testing edge cases (empty lists, huge datasets, null fields)',
      'Backend is unstable, slow, or unavailable',
    ],
  },
  'Modify': {
    changes_response: true,
    speed: 'Same as real API + modification overhead',
    determinism: 'Partially deterministic — real data with specific changes',
    use_when: [
      'Testing how the UI handles specific field values in otherwise real data',
      'Injecting one error field into an otherwise valid response',
      'Adding latency to simulate slow networks',
      'Modifying headers for auth/caching testing',
    ],
  },
};

console.log('cy.intercept modes:');
console.log('  Spy:    Watch requests — verify payloads and timing');
console.log('  Stub:   Replace responses — fast, deterministic, offline-capable');
console.log('  Modify: Alter real responses — test edge cases in real data');
Note: cy.intercept() only captures requests made by the browser (the application under test). It does NOT intercept requests made by cy.request() (which runs in the Cypress Node.js server). This distinction is important: use cy.request() for test setup (sending your own API calls) and cy.intercept() for monitoring and controlling the application’s API calls. They serve different purposes and operate on different network layers.
Tip: Start with spying. Before you stub anything, intercept the real requests to understand what the application actually sends and receives. cy.intercept('GET', '/api/**').as('anyApi') catches all GET requests to your API. This gives you a map of every API call the page makes — which endpoints, in what order, with what payloads. Use this map to decide which requests need stubbing for isolation and which should remain real for integration confidence.
Warning: cy.intercept() must be set up BEFORE the request is made. If you write cy.visit('/shop') and then cy.intercept('GET', '/api/products'), the intercept is registered after the page has already fired the request — it will not catch it. Always set up intercepts before the action that triggers the request: cy.intercept(...); then cy.visit(...);.

Common Mistakes

Mistake 1 — Setting up intercepts after the triggering action

❌ Wrong: cy.visit('/shop'); cy.intercept('GET', '/api/products').as('products'); — the intercept is too late; the request already fired.

✅ Correct: cy.intercept('GET', '/api/products').as('products'); cy.visit('/shop'); cy.wait('@products'); — intercept first, then trigger.

Mistake 2 — Stubbing everything when spying would be more appropriate

❌ Wrong: Replacing every API response with fixtures, making tests pass even when the real API is broken.

✅ Correct: Using spying (real responses) for integration tests and stubbing (fake responses) for isolated component tests. Balance both approaches for comprehensive coverage.

🧠 Test Yourself

Which cy.intercept mode should you use to test how your application displays an empty product list?