Network Mocking
Practice faking the server: errors, slow responses, timeouts, empty lists, broken data and dropped connections — with Playwright's page.route and Cypress's cy.intercept. Every recipe below is run against this page in our test suite.
The app under test
A product list that loads from /mock/products.json (and a greeting from /mock/profile.json). It shows a spinner while loading, and a different message for every kind of failure — that's what you'll fake with mocks.
Hi…
Loading products…
…
1. Fake a server error, then recover
The first call returns 500, the next one goes through to the real server. Check the error message, click Try again, and count the calls. Tip: scope role=alert to the app — Next.js adds its own hidden alert region to every page, so a page-wide getByRole('alert') finds two.
let calls = 0;
await page.route('**/mock/products.json*', async (route) => {
calls++;
if (calls === 1) return route.fulfill({ status: 500, body: 'Internal Server Error' });
return route.continue(); // let the real request through
});
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-app').getByRole('alert')).toContainText('The server had a problem (500)');
await page.getByRole('button', { name: 'Try again' }).click();
await expect(page.getByTestId('net-item')).toHaveCount(8);
expect(calls).toBe(2);2. Slow response — check the loading state
Delay the response by 2 seconds so the spinner is guaranteed to show, then check it disappears when the data arrives.
await page.route('**/mock/products.json*', async (route) => {
await new Promise((resolve) => setTimeout(resolve, 2000));
await route.continue();
});
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-loading')).toBeVisible();
await expect(page.getByTestId('net-item')).toHaveCount(8); // auto-waits up to 5 s
await expect(page.getByTestId('net-loading')).toBeHidden();3. Empty list
Return an empty array and check the empty state — a case that's hard to set up on a real server.
await page.route('**/mock/products.json*', (route) => route.fulfill({ json: [] }));
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-empty')).toHaveText('No products found.');
await expect(page.getByTestId('net-item')).toHaveCount(0);4. Your own test data (two endpoints)
Replace both the greeting and the product list with data you control, so the test never depends on what's on the server.
await page.route('**/mock/profile.json', (route) =>
route.fulfill({ json: { name: 'Test User', plan: 'Free' } }));
await page.route('**/mock/products.json*', (route) =>
route.fulfill({ json: [{ id: 1, name: 'Mocked Phone', price: 1, category: 'electronics' }] }));
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-profile')).toHaveText('Hi, Test User · Free plan');
await expect(page.getByTestId('net-item')).toHaveCount(1);
await expect(page.getByTestId('net-item')).toContainText('Mocked Phone');5. Network failure (offline)
Abort the request as if the connection dropped. The app should say it can't reach the server — not crash.
await page.route('**/mock/products.json*', (route) => route.abort('internetdisconnected'));
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-app').getByRole('alert')).toContainText("Can't reach the server");6. Timeout — without waiting 5 seconds
The app gives up after 5 seconds. Never answer the request, and move the page's clock forward instead of really waiting.
await page.clock.install(); // control the page's timers
await page.route('**/mock/products.json*', () => {}); // never respond
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-loading')).toBeVisible();
await page.clock.runFor(6000); // jump 6 seconds ahead
await expect(page.getByTestId('net-app').getByRole('alert')).toContainText('taking too long');7. Check the request the app sends
Change the category and prove the app asked for the right thing — here, the category query parameter.
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-item')).toHaveCount(8);
const requestPromise = page.waitForRequest((r) => r.url().includes('/mock/products.json?'));
await page.getByLabel('Category').selectOption('books');
const request = await requestPromise;
expect(request.method()).toBe('GET');
expect(new URL(request.url()).searchParams.get('category')).toBe('books');
await expect(page.getByTestId('net-item')).toHaveCount(3);8. Change part of a real response
Let the real request happen, then edit one field before the app sees it — useful for edge values like a price of ₹1.
await page.route('**/mock/products.json*', async (route) => {
const response = await route.fetch(); // the real response
const products = await response.json();
products[0].price = 1; // edit just one field
await route.fulfill({ response, json: products });
});
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-item')).toHaveCount(8);
await expect(page.getByTestId('net-item').first()).toContainText('₹1');9. Status codes & headers (401, 429 + Retry-After)
Each status should give the user a clear message. The 429 message reads the Retry-After header.
await page.route('**/mock/products.json*', (route) => route.fulfill({ status: 401 }));
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-app').getByRole('alert')).toContainText('Your session has expired');
await page.unroute('**/mock/products.json*');
await page.route('**/mock/products.json*', (route) =>
route.fulfill({ status: 429, headers: { 'Retry-After': '30' }, body: '' }));
await page.getByRole('button', { name: 'Try again' }).click();
await expect(page.getByTestId('net-app').getByRole('alert')).toContainText('try again in 30 seconds');10. Broken or unexpected data
Send JSON that doesn't parse, or JSON in the wrong shape. A robust app shows a friendly message instead of a blank page.
await page.route('**/mock/products.json*', (route) =>
route.fulfill({ status: 200, contentType: 'application/json', body: '{ not valid json' }));
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-app').getByRole('alert')).toContainText("couldn't read");
await page.unroute('**/mock/products.json*');
await page.route('**/mock/products.json*', (route) => route.fulfill({ json: { items: 'wrong shape' } }));
await page.getByRole('button', { name: 'Try again' }).click();
await expect(page.getByTestId('net-last')).toContainText('unexpected shape');