Stopping the Flake: Using cy.intercept() to Decouple Frontend Tests from Backend State
Learn how to use cy.intercept() in Cypress to eliminate flaky tests by stubbing network responses and simulating edge cases like 500 errors and network latency.
05 Mar 2026, 10:00 UTC

The Problem: The 'Fragile Green' Test
Frontend tests often fail not because the UI is broken, but because the backend is unpredictable. A database record was deleted by another test, a staging server is lagging, or a third-party API is rate-limiting your CI pipeline. When your tests depend on live network calls, you aren't just testing your frontend; you are testing the entire infrastructure chain.
The solution is to move from integration-heavy tests to isolated frontend tests using cy.intercept(). By stubbing network responses, you can force your application into specific states—like empty lists, server errors, or slow loading screens—without needing to manipulate a database.
How Interception Works
Cypress operates at the network layer within the browser. When you use cy.intercept(), Cypress tells the browser to watch for specific HTTP requests. If a request matches your criteria, Cypress can either let it pass through (spying), modify the request/response on the fly, or replace the backend response entirely with a static object (stubbing).
Spying vs. Stubbing
- Spying: The request goes to the real server, but Cypress records it. This is useful for verifying that the correct payload was sent to an API.
- Stubbing: Cypress intercepts the request and returns a predefined response immediately. The real server is never contacted.
Practical Implementation: Stubbing a User Profile
To avoid race conditions, you must define your intercepts before the action that triggers the network call. If you trigger the click first, the request might leave the browser before Cypress has registered the intercept.
In this example, we assume Cypress v12+ and a standard JSON fixture file located at cypress/fixtures/user.json.
// 1. Define the intercept before the action
// We use an alias (.as()) to track the request later
cy.intercept('GET', '/api/v1/user*', { fixture: 'user.json' }).as('getUser');
// 2. Trigger the action that causes the network request
cy.visit('/profile');
// 3. Explicitly wait for the intercepted call to finish
// This prevents the test from asserting before the UI updates
cy.wait('@getUser');
// 4. Assert that the UI rendered the data from the fixture
cy.get("[data-testid='username']").should('contain', 'Test User');
Diagnostic Check
To verify this is working, open the Cypress Test Runner. In the Command Log on the left, you will see the intercept call. When the request occurs, it will be labeled as (stubbed). If you don't see the (stubbed) label, the request hit the real server, meaning your URL pattern or HTTP method did not match.
Simulating Edge Cases
Stubbing isn't just for 'happy paths.' It is the most efficient way to test how your UI handles failure without actually breaking your server.
Simulating a Server Crash (500 Error)
cy.intercept('GET', '/api/v1/settings', {
statusCode: 500,
body: { error: 'Internal Server Error' },
}).as('getSettingsError');
cy.visit('/settings');
cy.wait('@getSettingsError');
cy.get('.error-toast').should('be.visible').and('contain', 'Something went wrong');
Simulating Network Latency
You can simulate a slow connection to test loading spinners or skeleton screens by adding a delay to the response:
cy.intercept('GET', '/api/v1/data', {
fixture: 'data.json',
delay: 2000 // Delay response by 2 seconds
});
The Trade-off: The API Contract Gap
The primary risk of heavy stubbing is the 'Contract Gap.' If the backend team changes a field name from user_name to username, your stubbed tests will stay green because they are using the old fixture. Your tests pass, but the production app crashes.
To mitigate this, do not stub every single test. Use a hybrid approach:
- Stubbed Tests: Use these for 90% of your UI logic, edge cases, and fast CI feedback.
- Contract/E2E Tests: Maintain a small set of 'smoke tests' that hit the real backend to ensure the API contract is still intact.
Verification and Rollback
Verification: Compare the execution time of a test suite using live APIs versus one using cy.intercept(). You should see a significant reduction in total runtime and a disappearance of 'flaky' failures caused by 503 or 504 timeouts.
Rollback: Since cy.intercept() does not modify the application code or the server state, there is no system rollback required. To return to live network calls, simply remove the cy.intercept() command from your test file.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.