INTEGRATION TESTING · LESSON 13 OF 24
Mock network responses and error states
Control a dependency while testing the interface that consumes it.
- page.route
- fulfill
- mocking
- error states
Your runnable checkpoint
Extract into a new practice folder. Each download contains tests, configuration, the complete local practice app and an offline lesson. Use Node.js 22 or 24. Each checkpoint is independent.
Install dependencies and browser binaries using the commands below. The test runner starts the practice server on port 4174. Tests target localhost, never the production EduPoly site.
1. Understand the concepts
Routing lets a test intercept a browser request and supply a controlled response. Register the route before the action that triggers the request. A mock installed too late can miss the event.
A mocked test verifies the UI’s behavior for a chosen contract; it does not prove that the real backend produces that contract. Keep separate integration or API checks for the real service.
Test a success response and a failure response. The lab validates that the response is an array of objects with titles. Invalid JSON shapes should also produce the visible error path.
Use specific URL patterns. A broad route that intercepts every request can accidentally mock scripts, styles or unrelated APIs and hide important behavior.
2. Build and run
These are complete test files and configuration. The download also includes app/ and server.mjs; preserve their paths. Stop any manually started practice server before running the suite.
npm install
npx playwright install chromium
npm run check
npm testtests/lesson.spec.ts
import { test, expect } from '@playwright/test';
test('render a controlled course response', async ({ page }) => {
await page.route('**/api/courses', route => route.fulfill({ json: [{ id: 7, title: 'Mocked course' }] }));
await page.goto('/');
await page.getByRole('button', { name: 'Load courses' }).click();
await expect(page.getByRole('list', { name: 'Courses', exact: true })).toHaveText('Mocked course');
});
test('show a server failure', async ({ page }) => {
await page.route('**/api/courses', route => route.fulfill({ status: 503, json: { error: 'Unavailable' } }));
await page.goto('/');
await page.getByRole('button', { name: 'Load courses' }).click();
await expect(page.locator('#course-status')).toContainText('Could not load courses');
});
playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
workers: process.env.CI ? 2 : undefined,
timeout: 30000,
expect: { timeout: 5000 },
reporter: [['list'], ['html', { open: 'never' }]],
use: { baseURL: 'http://127.0.0.1:4174', trace: 'retain-on-failure', screenshot: 'only-on-failure' },
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
webServer: { command: 'node server.mjs', url: 'http://127.0.0.1:4174', reuseExistingServer: false, timeout: 15000 }
});
package.json
{
"name": "edupoly-playwright-mock-network-responses",
"version": "1.0.0",
"private": true,
"scripts": {
"test": "playwright test",
"test:headed": "playwright test --headed",
"test:ui": "playwright test --ui",
"check": "tsc --noEmit",
"report": "playwright show-report",
"serve": "node server.mjs"
},
"devDependencies": {
"@playwright/test": "1.56.1",
"typescript": "5.9.3",
"@types/node": "22.20.5"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"moduleResolution": "Node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"types": [
"node"
],
"noEmit": true
},
"include": [
"tests/**/*.ts",
"pages/**/*.ts",
"playwright.config.ts"
]
}
.gitignore
node_modules/
playwright-report/
test-results/
playwright/.auth/
.env
3. Trace the code and check the result
- The handler is ready before navigation and the click.
- fulfill supplies a JSON response with appropriate serialization.
- The test asserts the visible result, not merely that a route handler ran.
Verify your result
- Success renders exactly the mocked title.
- HTTP 503 displays an error message.
4. Practice independently
Mock HTTP 200 with { unexpected: true } and check the error UI.
Show the explanation
Return json: { unexpected: true } and assert Could not load courses. This catches malformed-success responses.
When something goes wrong
If a browser executable is missing, run npx playwright install chromium in the project folder. If port 4174 is occupied, stop your manual practice server. Read assertion call logs for mismatched locators or expectations. Use npm run report to inspect failures; do not add fixed sleeps or broad resets to hide them.
