Stubbing API Responses in Cypress with cy.intercept
Learn how to control HTTP requests in Cypress tests with cy.intercept, see a worked example of stubbing a GET endpoint, and understand the trade-offs of over-stubbing.
15 Mar 2026, 12:02 UTC

Problem: Flaky tests due to real backend calls
When an end-to-end test relies on a live API, network latency, intermittent server errors, or changes in the backend can cause the test to fail unpredictably. This makes the test suite hard to trust and slows down feedback loops.
Thesis: Use cy.intercept to control network traffic deterministically
Cypress provides the cy.intercept command to stub, delay, or spy on HTTP requests without touching application code. By defining the exact response you want, you isolate the UI layer and make assertions reliable.
How cy.intercept works
The command matches a request by URL pattern, HTTP method, or both. You can return a static fixture, a dynamic response function, or simply let the request pass through while recording it for later inspection. Matched routes can be aliased (e.g., as('getUsers')) and waited on with cy.wait('@alias') to synchronize UI updates.
Setting up an intercept
Place the intercept before the action that triggers the request, typically in a beforeEach block or at the start of a test. The syntax is:
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers')
Where:
- Where to run: In any Cypress test file under
cypress/integration(e.g.,user_spec.js). No special permissions are needed beyond the ability to runnpx cypress openornpx cypress run. - Expected checks: After the intercept, the network tab in Chrome DevTools shows the request labeled "(intercepted)". The response body should match the contents of
fixtures/users.json. - Risks: If the URL pattern is too broad, unrelated requests may be stubbed, hiding real integration issues. Over-reliance on fixtures can mask contract drift between frontend and backend.
Worked example: Stubbing a user list endpoint
- Create a fixture file
cypress/fixtures/users.jsonwith an array of user objects, e.g.,[ { "id": 1, "name": "Ada" } ]. - In
cypress/integration/user_spec.js:
describe('User list page', () => {
beforeEach(() => {
// Stub the GET request to /api/users
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers')
cy.visit('/users')
})
it('renders the fixture data', () => {
// Wait for the intercepted request to complete
cy.wait('@getUsers')
// Assert that the UI shows the user name from the fixture
cy.contains('Ada')
})
it('handles error responses', () => {
// Override the intercept to simulate a 500 error
cy.intercept('GET', '/api/users', { statusCode: 500, body: { error: 'Internal Server Error' } })
cy.visit('/users')
cy.contains('Something went wrong')
})
})
- Run the test with
npx cypress openand select the spec file. - Open DevTools → Network tab; you should see the request to
/api/usersmarked as (intercepted) and the response matching the fixture or the error status you set. - Verify that the UI updates accordingly (list of users or error message).
Trade-off: Over-stubbing hides contract drift
While stubbing eliminates flakiness, it also prevents the test from detecting when the backend changes its response shape or status codes. A test that always receives a perfect fixture may pass even if the real API now returns a 401 or a different field name.
Actionable closing: Balance stubbing with occasional real requests
Use cy.intercept for the majority of your tests to achieve fast, deterministic runs. Periodically, add a test that disables stubbing (or uses a real endpoint against a staging environment) to verify that the contract still holds. This combination gives you confidence without sacrificing speed.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.