Skip to content

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.

Terminal
npm install
npx playwright install chromium
npm run check
npm test

tests/lesson.spec.ts

tests/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

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

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

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

.gitignore
node_modules/
playwright-report/
test-results/
playwright/.auth/
.env

3. Trace the code and check the result

  1. The handler is ready before navigation and the click.
  2. fulfill supplies a JSON response with appropriate serialization.
  3. 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.

Read the related official Playwright documentation ↗