Network Stubbing with cy.intercept
Spying on and stubbing API calls with cy.intercept, fixtures, waiting on aliases and testing error states.
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:
// 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:
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/:
// cypress/fixtures/comment.json
{
"id": 1,
"name": "From a fixture",
"email": "fixture@example.com",
"body": "Loaded from cypress/fixtures/comment.json"
}
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:
// 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:
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:
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.