Cypress Lesson 3 of 6 ⏱️ 4 min read

Network Stubbing with cy.intercept

Spying on and stubbing API calls with cy.intercept, fixtures, waiting on aliases and testing error states.

Updated Oct 30, 2026

Why control the network?

Modern web apps load data from APIs. If your UI tests depend on a real backend, they become slow and flaky — and some states (server errors, empty lists, slow responses) are nearly impossible to reproduce on demand. cy.intercept() lets you spy on real requests or stub them with fake responses.

The examples use Cypress's demo app (baseUrl: 'https://example.cypress.io'). Its network page has buttons that call a public JSON API.

Spying: waiting for a real request

Instead of cy.wait(3000), wait for the actual request to finish:

Javascript
// cypress/e2e/network-spy.cy.js
describe('Spying on requests', () => {
  beforeEach(() => cy.visit('/commands/network-requests'));

  it('waits for the comment request and checks the response', () => {
    cy.intercept('GET', '**/comments/*').as('getComment');

    cy.get('.network-btn').click();

    cy.wait('@getComment').its('response.statusCode').should('be.oneOf', [200, 304]);
    cy.get('.network-comment').should('not.be.empty');
  });

  it('inspects the request body of a POST', () => {
    cy.intercept('POST', '**/comments').as('postComment');

    cy.get('.network-post').click();

    cy.wait('@postComment').then(({ request, response }) => {
      expect(request.body).to.include('email');
      expect(response.statusCode).to.eq(201);
    });
    cy.get('.network-post-comment').should('contain', 'POST successful!');
  });
});

Key ideas:

  • Register the intercept before the action that triggers the request.
  • .as('name') gives it an alias; cy.wait('@name') waits for it and yields the request and response.
  • **/comments/* is a glob — it matches any host and any comment ID.

Stubbing: returning a fake response

Pass a response as the third argument and the request never reaches the server:

Javascript
it('shows a stubbed comment', () => {
  cy.intercept('GET', '**/comments/*', {
    statusCode: 200,
    body: { id: 1, name: 'Stubbed', email: 'qa@example.com', body: 'This comment came from the test.' },
  }).as('getComment');

  cy.visit('/commands/network-requests');
  cy.get('.network-btn').click();
  cy.wait('@getComment');

  cy.get('.network-comment').should('contain', 'This comment came from the test.');
});

Using fixtures

Keep larger stub data in cypress/fixtures/:

JSON
// cypress/fixtures/comment.json
{
  "id": 1,
  "name": "From a fixture",
  "email": "fixture@example.com",
  "body": "Loaded from cypress/fixtures/comment.json"
}
Javascript
cy.intercept('GET', '**/comments/*', { fixture: 'comment.json' }).as('getComment');

Testing error and edge states

Stubbing is the easiest way to test how the UI handles failures:

Javascript
// Server error
cy.intercept('GET', '/api/products', { statusCode: 500, body: { message: 'Server error' } }).as('products');
cy.visit('/shop');
cy.wait('@products');
cy.get('[data-cy=error-banner]').should('contain', 'Something went wrong');

// Empty list
cy.intercept('GET', '/api/products', { statusCode: 200, body: [] });
cy.visit('/shop');
cy.get('[data-cy=empty-state]').should('be.visible');

// Slow network — is a loading spinner shown?
cy.intercept('GET', '/api/products', { delay: 2000, fixture: 'products.json' });
cy.visit('/shop');
cy.get('[data-cy=spinner]').should('be.visible');
cy.get('[data-cy=spinner]').should('not.exist');

// Network failure (offline)
cy.intercept('GET', '/api/products', { forceNetworkError: true });

(These use a hypothetical /shop page — adapt the URLs and data-cy selectors to your app.)

Modifying real responses

Sometimes you want the real backend but with one field changed:

Javascript
cy.intercept('GET', '/api/profile', (req) => {
  req.continue((res) => {
    res.body.plan = 'premium';     // test the premium UI without a premium account
  });
});

Asserting on request details

Verify your app sends the right data — a common source of bugs:

Javascript
cy.intercept('POST', '/api/orders').as('createOrder');
cy.get('[data-cy=place-order]').click();

cy.wait('@createOrder').its('request.body').should('deep.include', {
  currency: 'USD',
  quantity: 2,
});

Stub or not?

Stub the network when… Use the real backend when…
Testing UI states (errors, empty, loading) Running a few true end-to-end smoke tests
The backend is slow, unstable or third-party Verifying the frontend and API agree
You need fast, deterministic tests Testing data that must persist

A healthy suite does both: mostly stubbed UI tests, plus a small number of real end-to-end journeys. Next: reusing logic with custom commands and fixtures.

Finished this lesson?
Tick it off to track your Cypress progress.
Next lesson