Cypress interview question
How do you use cy.intercept to wait on, spy on and stub network requests?
Short answer
Register cy.intercept before the action that triggers the request, alias it, then cy.wait('@alias') to synchronise and assert on the request and response. Pass a response or fixture to stub it, or use a handler with req.reply / req.continue to modify traffic. Keep some tests unstubbed end to end.
Answer
cy.intercept() lets Cypress observe and control HTTP traffic between the app and the server. It covers three jobs: waiting, spying and stubbing.
1. Waiting on real requests
Register the intercept before the action that fires the request, give it an alias and wait on it. This replaces arbitrary cy.wait(3000) calls.
it('searches products', () => {
cy.intercept('GET', '/api/products*').as('search');
cy.visit('/products');
cy.get('[data-cy=search]').type('lamp{enter}');
cy.wait('@search').then(({ request, response }) => {
expect(request.url).to.include('q=lamp');
expect(response.statusCode).to.eq(200);
});
cy.get('[data-cy=product-card]').should('have.length.greaterThan', 0);
});
If you intercept after clicking, a fast request may already have completed and cy.wait('@search') will time out.
2. Spying
The same aliased intercept doubles as a spy: assert the request body sent by the front end, headers, or that a call happened a specific number of times with cy.get('@search.all').should('have.length', 1).
3. Stubbing responses
// Static response from a fixture file (cypress/fixtures/products-empty.json)
cy.intercept('GET', '/api/products*', { fixture: 'products-empty.json' }).as('search');
// Inline response, useful for error states
cy.intercept('POST', '/api/checkout', {
statusCode: 503,
body: { error: 'Service unavailable' },
}).as('checkout');
cy.get('[data-cy=pay]').click();
cy.wait('@checkout');
cy.contains('[role=alert]', 'try again').should('be.visible');
Dynamic handlers
A function handler gives full control: modify the outgoing request, pass it through and modify the response, or reply conditionally.
cy.intercept('GET', '/api/profile', (req) => {
req.headers['x-feature-flag'] = 'new-profile';
req.continue((res) => {
res.body.plan = 'premium'; // force a state that is hard to set up for real
});
});
cy.intercept('GET', '/api/slow-report', (req) => {
req.reply({ delay: 2000, fixture: 'report.json' }); // test loading spinners
});
cy.intercept('GET', '/api/feed', { forceNetworkError: true }); // offline behaviour
Matching details
- URL matching accepts glob patterns, regular expressions or a
RouteMatcherobject ({ method, pathname, query }). - When several intercepts match, the most recently defined one is applied first, so a test-level stub can override a
beforeEachdefault. - Intercepts are cleared between tests automatically.
When not to stub
Stubbing makes UI tests fast and deterministic and lets you reach states like 500 errors or empty lists. The risk is drift: your fixtures say the API returns items, the real API now returns results, and every stubbed test still passes. I keep a small set of unstubbed end-to-end journeys against a real backend, and keep fixtures honest with API contract tests or schema checks.
What interviewers look for
- Ordering: intercept first, action second, wait third.
- Using aliases to synchronise rather than fixed waits.
- Knowing static stubs vs handlers, and simulating errors, delays and network failures.
- Awareness that heavy stubbing needs a strategy against drift.
A strong answer covers
- Define the intercept before the triggering action
- Alias and cy.wait('@alias') instead of fixed waits
- Stub with fixtures or static responses (statusCode, body)
- Use handlers (req.reply, req.continue) to modify or simulate errors/delays
- Balance: stubbed tests plus some real end-to-end