Deterministic API Testing in Cypress with cy.intercept()
Replace real API calls with deterministic stubs in Cypress using cy.intercept(). Follow a clear workflow to define, trigger, and verify interceptions, simulate errors, and recover from common pitfalls.
06 Jul 2025, 03:31 UTC

Problem: Flaky End‑to‑End Tests Tied to Real Backend Calls
When a test depends on an external API, network latency, server errors, or malformed data can cause failures that are unrelated to the application logic. Eliminating that dependency makes tests repeatable, faster, and easier to debug.
Desired Outcome
Replace live HTTP requests with controlled, deterministic responses so that:
- Tests run consistently regardless of network conditions.
- Edge cases such as 5xx errors, timeouts, or invalid JSON can be simulated.
- Test suites are isolated from the real backend, allowing them to run offline.
Prerequisites
- Cypress 6.0.0 or newer (cy.intercept was introduced in this version).
- A Cypress project with a
cypress.config.js(or.ts) file. - Basic knowledge of the test file structure (
cypress/e2eorcypress/component). - Optionally, a
cypress/fixturesfolder if you want to load static data.
Procedure
1. Define the Interception Early
Place cy.intercept() before the action that triggers the request. If the request is sent during the initial page load, put the interception in cypress/support/e2e.js or inside a beforeEach hook that runs before cy.visit().
// cypress/support/e2e.js
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers');
2. Use Aliases for Synchronization
Assign an alias with as() so you can wait for and assert on the request later.
cy.visit('/dashboard');
cy.wait('@getUsers');
3. Static vs. Dynamic Responses
Static responses are simple objects or fixture references. They are serialized once, so mutating the returned object in a test can affect other tests. Use a factory function or JSON.parse(JSON.stringify(...)) to clone the response for each invocation.
const makeUserResponse = () => JSON.parse(JSON.stringify({
users: [{ id: 1, name: 'Alice' }]
}));
cy.intercept('GET', '/api/users', (req) => {
req.reply(makeUserResponse());
}).as('getUsers');
4. Simulating Errors and Timeouts
Pass a function to cy.intercept() to customize status codes, delays, or body content. This is useful for testing error handling logic.
// Simulate a 500 error on the first call, then a 200 on the second
let firstCall = true;
cy.intercept('GET', '/api/data', (req) => {
if (firstCall) {
req.reply({ statusCode: 500, body: { error: 'Server error' } });
firstCall = false;
} else {
req.reply({ statusCode: 200, body: { data: 'ok' } });
}
}).as('getData');
5. Adding a Delay to Test Loading States
cy.intercept('GET', '/api/slow', (req) => {
req.reply({ statusCode: 200, body: { slow: true }, delay: 2000 });
}).as('slow');
Expected Checks
- The command log shows the alias with a green checkmark after
cy.wait()resolves. - Assertions on
request.bodyorresponse.statusCodepass. - No "Unhandled request" warnings appear in the Cypress log.
- In DevTools, the intercepted request appears with a Cypress badge or as "(from ServiceWorker)" indicating it was stubbed.
- Running the test with the real backend stopped still passes, proving no external dependency.
Recovery Options
- Broaden the URL pattern: If the request never matches, try a glob (
**/api/**) or a RegExp. - Check the HTTP method:
GETvsPOSTmust match exactly. - Verify baseUrl: If
baseUrlis set incypress.config.js, include it in the pattern or usecy.intercept('/api/...')without the domain. - Adjust the order of commands: Ensure
cy.intercept()runs before the request is fired (beforecy.visit()or the component mount). - Increase timeout: Use
cy.wait('@alias', { timeout: 10000 })if the request is delayed.
Cautions
- Static response objects are shared across tests; always clone or use a factory function.
- Wildcard patterns can match unintended endpoints; prefer explicit paths or RegExp.
- Requests made before
cy.intercept()runs (e.g., initial page load assets) are not captured; place intercepts insupport/e2e.jsif needed. - When stubbing, the response bypasses browser caching; set
Cache-Controlheaders manually if your app relies on them. - Component tests may not trigger network calls until the component mounts; put
cy.intercept()beforecy.mount().
Summary
Using cy.intercept() you can turn flaky, network‑dependent tests into deterministic, isolated ones. Define interceptions early, use aliases for synchronization, and provide static or dynamic responses as needed. Verify the stubbed behavior in both the Cypress command log and DevTools, and be prepared to adjust patterns or order if the request never matches.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.