Mock API Responses (route.fulfill — trả dữ liệu giả cho request)
automationplaywright
Mock API trong Playwright là dùng page.route kết hợp route.fulfill để trả response tuỳ ý (JSON, file, status code) thay vì để request đến server thật. Kỹ thuật này giúp FE test độc lập với backend, tái hiện edge case (lỗi 500, data rỗng, data khổng lồ) và làm test ổn định, nhanh hơn rất nhiều.
Khái niệm & Bản chất
- Mock response = khi app gọi API, Playwright trả về response do bạn định nghĩa sẵn. Browser/app hoàn toàn không biết đó là data giả — nó vẫn parse JSON, render UI, chạy logic như thường.
- route.fulfill() là API kết thúc route bằng một response giả: có
status(mặc định 200),headers,body(string),json(object/array tự stringify + set content-type),path(đọc từ file). - Mock vs Stub khác nhau: stub nghĩa truyền thống là thay hàm JS (VD: giả
fetch) — code app bị sửa. Mock ở tầng network của Playwright không động vào code app, nên test sát production hơn. - Override response thật: kết hợp
route.fetch()(gọi API thật) → sửa body →route.fulfill()— pattern 'modify on the fly' cho phép giữ 99% data thật, chỉ đổi field cần test.
Cách hoạt động & API chính
- Luồng:
page.route(pattern, handler)→ trong handler gọiroute.fulfill({ status, json })→ request của app nhận response giả ngay, không đi ra mạng. - fulfill với json:
route.fulfill({ json: { id: 1, name: 'Test' } })— Playwright tự setContent-Type: application/json. - fulfill với file:
route.fulfill({ path: 'fixtures/big-list.json' })— tốt cho payload lớn, dễ review trong git. - fulfill sau route.fetch:
const res = await route.fetch(); const body = await res.json(); ...; route.fulfill({ response: res, json: body })— giữ nguyên status/headers thật, chỉ thay body. - Giả lập lỗi:
route.fulfill({ status: 500, json: { message: 'Server error' } })hoặcroute.abort('timedout')cho lỗi tầng network.
Ví dụ & Cú pháp thực tế
- ```ts import { test, expect } from '@playwright/test'; test('mock danh sách user', async ({ page }) => { // Trả data giả cho API GET /api/users await page.route('**/api/users', (route) => { route.fulfill({ status: 200, json: [ { id: 1, name: 'An Nguyen', role: 'admin' }, { id: 2, name: 'Binh Tran', role: 'user' }, ], }); }); await page.goto('/users'); await expect(page.getByText('An Nguyen')).toBeVisible(); await expect(page.getByText('Binh Tran')).toBeVisible(); });
- ```ts test('mock lỗi 500 để test UI error state', async ({ page }) => { await page.route('**/api/orders', (route) => route.fulfill({ status: 500, json: { message: 'Internal Server Error' }, }) ); await page.goto('/orders'); // Kiểm tra UI hiển thị thông báo lỗi đúng await expect(page.getByText('Không tải được đơn hàng')).toBeVisible(); await expect(page.getByRole('button', { name: 'Thử lại' })).toBeEnabled(); });
- ```ts test('giữ data thật, chỉ sửa field cần test', async ({ page }) => { await page.route('**/api/products/42', async (route) => { const response = await route.fetch(); // gọi API thật const product = await response.json(); product.stock = 0; // ép hết hàng await route.fulfill({ response, json: product }); }); await page.goto('/products/42'); await expect(page.getByText('Hết hàng')).toBeVisible(); await expect(page.getByRole('button', { name: 'Mua' })).toBeDisabled(); });
- ```ts // Mock từ file fixture — payload lớn dễ quản lý await page.route('**/api/report/full', (route) => route.fulfill({ path: 'tests/fixtures/report-10k-rows.json' }) );
- ```ts // Mock phân biệt theo HTTP method await page.route('**/api/items', (route) => { if (route.request().method() === 'POST') { route.fulfill({ status: 201, json: { id: 999 } }); } else { route.fulfill({ json: [] }); // GET trả list rỗng } });
Best practices & Khi nào dùng
- Dùng khi: backend chưa sẵn sàng (FE đi trước), cần data edge case (rỗng, cực dài, ký tự lạ, Unicode), cần test lỗi khó ép backend tái hiện, chạy regression nhanh ổn định không phụ thuộc môi trường.
- KHÔNG mock thứ bạn đang muốn verify: mock API login để test flow đăng nhập là vô nghĩa — test sẽ luôn xanh. Mock chỉ nên cho phần ngoài phạm vi test.
- Đặt fixture theo domain:
fixtures/api/users.success.json,users.empty.json,users.error.json— đặt tên nói rõ kịch bản. - Giữ mock data sát schema thật: lấy response thật capture từ network tab làm mẫu. Mock lệch schema → test xanh nhưng production vẫn hỏng.
- Phối hợp setup/teardown: đăng ký mock trong
test.beforeEachcho suite cần data cố định; testcase riêng có thểpage.routethêm hoặcpage.unrouteđể thay đổi.
Gotchas, So sánh & Lưu ý
- Mock quá nhiều = test ảo: nếu mọi API đều mock, bạn đang test UI với data tưởng tượng — không phát hiện được contract change giữa FE/BE. Cân bằng bằng cách giữ một số test integration gọi API thật.
- Timing race: đăng ký route sau
goto→ request đã bắn ra sẽ không bị mock. Luôn route trước khi action trigger request. - fulfill không qua CORS/preflight: request POST với JSON tới origin khác có thể sinh OPTIONS preflight mà mock không xử lý — nếu thấy request treo, route thêm pattern cho OPTIONS.
- So với MSW (Mock Service Worker): MSW chạy trong browser qua Service Worker, dùng chung được cho cả dev và test; Playwright route đơn giản hơn, không cần setup, nhưng chỉ tồn tại trong test. Dự án lớn đôi khi dùng cả hai.
- json option tự stringify: truyền object vào
jsonchứ đừng tựJSON.stringifyrồi bỏ vàobody— dễ quên Content-Type. - status mặc định 200: muốn test 201/204/404 phải set
statustường minh — 204 không có body, đừng truyền json.
