Mục lục
- Mục Tiêu Bài Học
- Modify Real vs Full Mock
- Cú Pháp Cơ Bản
- fulfill({ response, json }) — Cơ Chế Override
- Use Case 1 — Test Edge Case Với Subscription Tier
- Use Case 2 — Feature Flag Test
- Use Case 3 — Inject Empty State
- Use Case 4 — Inject Error In Field
- Use Case 5 — A/B Variant
- Patch Array Items
- Conditional Modify
- Header Override
- Limitation Của Pattern Modify Real
- 4 Pitfalls
- Tổng Kết
- Quiz 5 Câu
- Bài Tiếp Theo
Mục Tiêu Bài Học
- Phân biệt rõ "modify real" và "full mock" — khi nào mỗi cách phù hợp hơn.
- Nắm cú pháp
route.fetch() → patch JSON → route.fulfill({ response, json }). - Hiểu cơ chế
fulfill({ response, json }):responselàm baseline cho status/headers,jsonoverride body. - Viết được các pattern thực tế: inject empty state, inject error field, patch array, conditional modify, header override.
- Liệt kê limitation của pattern modify real (backend phải accessible, schema thay đổi cần update patch).
- Tránh 4 pitfall: quên truyền
responsevào fulfill, patch field không tồn tại, backend down không có fallback, mutate nested object gây side-effect.
Modify Real vs Full Mock
Có hai cách để kiểm soát response trong Playwright route handler:
Full Mock
Dùng route.fulfill({ json: {...} }) với data tự soạn hoàn toàn. Không gọi server. Kiểm soát tuyệt đối response — phù hợp khi test cần isolation hoặc backend chưa có.
// Full mock — data hardcode, không gọi server
await page.route('**/api/user', (route) => {
route.fulfill({
json: {
id: 1,
name: 'Test User',
subscription: 'premium',
featureFlags: { newDashboard: true },
},
});
});
Nhược điểm của full mock: phải tự soạn đầy đủ toàn bộ schema. Khi backend thêm field mới hoặc đổi cấu trúc, mock data cần cập nhật thủ công — tốn công maintain, dễ lỗi drift giữa mock và production.
Modify Real
Fetch response thật từ backend → patch một vài field → fulfill về browser. Giữ nguyên phần lớn data production, chỉ override đúng field cần thiết cho test.
// Modify real — fetch thật, patch 2 field, giữ nguyên phần còn lại
await page.route('**/api/user', async (route) => {
const response = await route.fetch(); // gọi backend thật
const json = await response.json();
json.subscription = 'premium'; // patch field test
json.featureFlags.newDashboard = true;
await route.fulfill({ response, json }); // trả về browser với data modified
});
Ưu điểm: data structure khớp backend thật. Các field không patch giữ nguyên giá trị production — test hoạt động với context gần thực tế hơn. Khi schema backend thêm field mới, không cần cập nhật gì nếu không cần patch field đó.
| Tiêu chí | Full Mock | Modify Real |
|---|---|---|
| Gọi backend? | Không | Có |
| Data sát production? | Không (tự soạn) | Có (backend trả) |
| Maintain khi schema đổi? | Phải cập nhật toàn bộ mock | Chỉ cập nhật field patch |
| Kiểm soát response? | Tuyệt đối | Chỉ field được patch |
| Phụ thuộc backend? | Không | Có — backend phải accessible |
| Phù hợp khi? | Test isolation, backend chưa có, endpoint phức tạp | Test edge case, feature flag, A/B — cần data sát thật |
Cú Pháp Cơ Bản
Ba bước: fetch response thật → parse JSON → patch field → fulfill.
await page.route('**/api/user', async (route) => {
// Bước 1: fetch response thật từ backend
const response = await route.fetch();
// Bước 2: parse JSON
const json = await response.json();
// Bước 3: patch field cần test
json.subscription = 'premium';
json.featureFlags.newDashboard = true;
// Bước 4: fulfill — giữ status/headers từ response thật, override body
await route.fulfill({ response, json });
});
Trong test đầy đủ:
import { test, expect } from '@playwright/test';
test('premium user sees new dashboard', async ({ page }) => {
await page.route('**/api/user', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.subscription = 'premium';
json.featureFlags.newDashboard = true;
await route.fulfill({ response, json });
});
await page.goto('/dashboard');
await expect(page.getByTestId('new-dashboard')).toBeVisible();
});
Điểm quan trọng: handler phải là async vì cả route.fetch() và response.json() đều trả Promise. Quên async sẽ gây lỗi runtime.
fulfill({ response, json }) — Cơ Chế Override
route.fulfill có ba dạng dùng khi modify real:
{ response } — Pass-through giữ nguyên
Dùng status, headers, body từ response thật. Không override gì.
await route.fulfill({ response });
// Giống route.continue() về kết quả, nhưng đi qua test để inspect
{ response, json } — Override body giữ status/headers
Status và headers lấy từ response thật. Body được thay bằng json (Playwright tự stringify và set content-type: application/json).
const json = await response.json();
json.role = 'admin';
await route.fulfill({ response, json });
// → status: như server trả (vd 200)
// → headers: như server trả (cache-control, set-cookie, custom headers)
// → body: JSON.stringify(json) với field đã patch
{ response, body } — Override body dạng string/Buffer
Tương tự json nhưng dùng khi body không phải JSON (text, HTML, binary) hoặc khi cần kiểm soát định dạng serialize thủ công.
const text = await response.text();
const patched = text.replace('free_tier', 'premium_tier');
await route.fulfill({ response, body: patched });
Tóm tắt priority
Khi truyền cả response lẫn field override, field override thắng:
response: baseline — status, headers, body từ backend.jsonhoặcbody: override body, tự setcontent-typenếu dùngjson.status: override status code.headers: override headers (không merge — thay hẳn).
// Override status + thêm header + patch body
await route.fulfill({
response, // baseline
status: 200, // override status
headers: {
...response.headers(), // giữ headers gốc
'x-test-patched': 'true', // thêm header test
},
json: patchedData, // override body
});
Use Case 1 — Test Edge Case Với Subscription Tier
Test cần verify UI khi user có tier "premium" nhưng không muốn seed user premium vào DB test. Fetch user thật từ backend rồi patch field subscription.
test('premium badge shows on profile', async ({ page }) => {
await page.route('**/api/user', async (route) => {
const response = await route.fetch();
const json = await response.json();
// Patch tier để test premium UI — giữ nguyên id, name, avatar, etc.
json.subscription = 'premium';
json.subscriptionExpiresAt = '2027-01-01';
await route.fulfill({ response, json });
});
await page.goto('/profile');
await expect(page.getByTestId('premium-badge')).toBeVisible();
await expect(page.getByText('Premium')).toBeVisible();
});
Lợi ích so với full mock: không cần soạn lại toàn bộ user object. Các field như id, name, avatar, createdAt giữ nguyên từ backend — nếu UI hiển thị những field này, test vẫn hoạt động đúng.
Use Case 2 — Feature Flag Test
Backend trả config object với feature flags. Test cần bật flag newDashboard để verify UI mới — không cần deploy backend hay thay đổi config production.
test('new dashboard renders when flag enabled', async ({ page }) => {
await page.route('**/api/user', async (route) => {
const response = await route.fetch();
const json = await response.json();
// Patch nested object feature flags — giữ các flag khác
json.featureFlags = {
...json.featureFlags, // giữ nguyên flags hiện có
newDashboard: true, // bật flag cần test
};
await route.fulfill({ response, json });
});
await page.goto('/');
await expect(page.getByTestId('new-dashboard-widget')).toBeVisible();
});
test('old dashboard when flag disabled', async ({ page }) => {
await page.route('**/api/user', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.featureFlags = {
...json.featureFlags,
newDashboard: false, // tắt flag
};
await route.fulfill({ response, json });
});
await page.goto('/');
await expect(page.getByTestId('classic-dashboard')).toBeVisible();
});
Pattern { ...json.featureFlags, newDashboard: true } giữ các flag khác nguyên — quan trọng vì UI có thể phụ thuộc nhiều flag cùng lúc. Nếu gán thẳng json.featureFlags = { newDashboard: true }, tất cả flag còn lại mất → test có thể fail vì reason không liên quan.
Use Case 3 — Inject Empty State
Test UI khi list rỗng — trường hợp thường khó reproduce vì DB test có sẵn data. Fetch response thật rồi override body thành empty array.
test('empty orders page shows empty state message', async ({ page }) => {
await page.route('**/api/orders', async (route) => {
const response = await route.fetch();
// Override body thành list rỗng — giữ status 200 và headers từ response thật
await route.fulfill({ response, json: [] });
});
await page.goto('/orders');
await expect(page.getByText('Bạn chưa có đơn hàng nào')).toBeVisible();
await expect(page.getByTestId('empty-state-illustration')).toBeVisible();
});
Không cần parse body trước — bỏ qua response.json() vì không cần inspect body cũ. Truyền thẳng json: [] để override. Status 200 và headers giữ nguyên từ backend — đảm bảo app code không nhầm thành lỗi.
Tương tự cho object rỗng:
await page.route('**/api/notifications/unread', async (route) => {
const response = await route.fetch();
// Test UI khi không có notification nào
await route.fulfill({ response, json: { items: [], total: 0 } });
});
Use Case 4 — Inject Error In Field
Simulate trạng thái lỗi ở field cụ thể — status HTTP vẫn 200 (backend xử lý thành công) nhưng payload chứa error state trong field. Phổ biến với payment, verification, third-party integration.
test('card declined error UI renders correctly', async ({ page }) => {
await page.route('**/api/payment', async (route) => {
const response = await route.fetch();
const json = await response.json();
// Patch trạng thái lỗi — giữ nguyên các field khác (transactionId, amount, etc.)
json.status = 'failed';
json.errorCode = 'CARD_DECLINED';
json.errorMessage = 'Thẻ bị từ chối. Vui lòng kiểm tra lại.';
await route.fulfill({ response, json });
});
await page.goto('/checkout');
await page.getByRole('button', { name: 'Thanh toán' }).click();
await expect(page.getByTestId('payment-error')).toBeVisible();
await expect(page.getByText('CARD_DECLINED')).toBeVisible();
});
Pattern này test khả năng xử lý lỗi của UI mà không cần backend thật trả lỗi — backend vẫn trả 200 với data hợp lệ, test chỉ patch field status và errorCode. Dùng để test resilience của app với các error state hiếm gặp.
Inject Missing Field — Test Default Behavior
Xóa field để test app xử lý missing data:
test('avatar fallback when no avatar field', async ({ page }) => {
await page.route('**/api/user', async (route) => {
const response = await route.fetch();
const json = await response.json();
delete json.avatarUrl; // xóa field để test fallback
await route.fulfill({ response, json });
});
await page.goto('/profile');
await expect(page.getByAltText('Default avatar')).toBeVisible();
});
Use Case 5 — A/B Variant
Test app theo từng variant A/B mà không phụ thuộc backend assign variant. Force variant qua patch response.
test('variant B shows new CTA button text', async ({ page }) => {
await page.route('**/api/experiment/config', async (route) => {
const response = await route.fetch();
const json = await response.json();
// Force variant B — giữ nguyên các experiment config khác
json.experiments = {
...json.experiments,
ctaButtonText: 'variant_b', // force variant cần test
};
await route.fulfill({ response, json });
});
await page.goto('/landing');
await expect(page.getByRole('button', { name: 'Bắt đầu miễn phí' })).toBeVisible();
});
test('variant A shows original CTA button text', async ({ page }) => {
await page.route('**/api/experiment/config', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.experiments = {
...json.experiments,
ctaButtonText: 'variant_a',
};
await route.fulfill({ response, json });
});
await page.goto('/landing');
await expect(page.getByRole('button', { name: 'Đăng ký ngay' })).toBeVisible();
});
Pattern này loại bỏ sự không ổn định khi test A/B — không còn phụ thuộc vào logic phân chia random của backend. Mỗi test kiểm soát chính xác variant nào được hiển thị.
Patch Array Items
Khi response là array hoặc chứa array, dùng .map() để patch mỗi item — tránh mutate trực tiếp từng phần tử trong loop.
// Patch tất cả items — thêm discount cho mọi sản phẩm
await page.route('**/api/products', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.items = json.items.map((item: { id: number; price: number }) => ({
...item, // giữ nguyên các field gốc
discount: 50, // thêm field discount
discountedPrice: item.price * 0.5,
}));
await route.fulfill({ response, json });
});
Patch item đầu tiên — test UI với sản phẩm hết hàng:
await page.route('**/api/products', async (route) => {
const response = await route.fetch();
const json = await response.json();
// Chỉ patch item đầu để test out-of-stock UI
if (json.items && json.items.length > 0) {
json.items = [
{ ...json.items[0], outOfStock: true, stock: 0 }, // patch item đầu
...json.items.slice(1), // giữ nguyên phần còn lại
];
}
await route.fulfill({ response, json });
});
Pattern { ...item, newField: value } (spread rồi override) tạo object mới, không mutate item gốc trong array. Điều này quan trọng khi một route handler bị đăng ký nhiều lần (vd test với page.route kết hợp context.route) — tránh side-effect cross-handler.
Conditional Modify
Patch chỉ khi data thỏa điều kiện — tránh lỗi nếu response không có data mong đợi.
await page.route('**/api/products', async (route) => {
const response = await route.fetch();
const json = await response.json();
// Chỉ patch khi có sản phẩm — không lỗi khi list rỗng
if (json.products && json.products.length > 0) {
json.products[0].outOfStock = true; // test out-of-stock UI cho item đầu
}
await route.fulfill({ response, json });
});
Conditional dựa trên giá trị field:
await page.route('**/api/orders', async (route) => {
const response = await route.fetch();
const json = await response.json();
// Inject trạng thái delay cho order đang xử lý
json.orders = json.orders.map((order: { status: string; id: string }) => {
if (order.status === 'processing') {
return { ...order, estimatedDelay: '2 ngày', delayReason: 'WAREHOUSE_ISSUE' };
}
return order; // giữ nguyên order status khác
});
await route.fulfill({ response, json });
});
Kiểm tra json.products && json.products.length > 0 trước khi patch là bắt buộc — backend có thể trả list rỗng hoặc field không tồn tại trong một số điều kiện. Thiếu guard → lỗi runtime Cannot read properties of undefined.
Header Override
Ngoài body, đôi khi cần patch header — vd disable cache để test không bị stale, hay thêm custom header để debug.
await page.route('**/api/products', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.items[0].featured = true;
await route.fulfill({
response,
json,
headers: {
...response.headers(), // giữ nguyên headers gốc
'cache-control': 'no-cache', // disable cache cho test
'x-test-modified': 'true', // marker debug
},
});
});
Lưu ý: headers trong fulfill là replace — không merge với headers từ response. Phải dùng ...response.headers() để giữ lại headers gốc rồi thêm/override field cần thiết. Nếu bỏ spread, tất cả headers như content-type, set-cookie, authorization từ backend đều mất.
Trường hợp chỉ muốn override header, không cần patch body:
await page.route('**/api/config', async (route) => {
const response = await route.fetch();
await route.fulfill({
response,
headers: {
...response.headers(),
'x-ratelimit-remaining': '0', // simulate rate limit header
},
});
});
Limitation Của Pattern Modify Real
- Backend phải accessible. Test không chạy được khi backend down hoặc không có network (CI offline). Full mock không có hạn chế này. Cần fallback handler hoặc cân nhắc dùng full mock cho test isolation.
- Backend slow ảnh hưởng test.
route.fetch()chờ backend trả response thật — backend có latency cao thì test chậm theo. Không kiểm soát được như full mock (fulfill ngay lập tức). - Schema thay đổi cần cập nhật patch logic. Nếu backend đổi tên field (vd
subscription→plan), patch vào field cũ không có tác dụng — test fail hoặc silent wrong. Cần review patch logic khi backend release schema mới. - Không phù hợp với endpoint có side-effect.
route.fetch()gọi backend thật — POST/PUT/DELETE sẽ tạo/sửa/xóa data thật trong DB test. Với endpoint không idempotent, nên dùng full mock để tránh side-effect tích lũy qua nhiều lần chạy test.
4 Pitfalls
Pitfall 1 — Quên Truyền response Vào fulfill
Bỏ qua response trong fulfill({ json }) → mất status và headers từ backend thật. Playwright sẽ dùng status 200 mặc định và headers tối thiểu. Nếu app phụ thuộc header như set-cookie, cache-control, hay custom header auth, behavior sẽ sai.
// SAI — mất status và headers thật
await page.route('**/api/user', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.role = 'admin';
await route.fulfill({ json }); // thiếu response!
});
// ĐÚNG — giữ status và headers thật
await page.route('**/api/user', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.role = 'admin';
await route.fulfill({ response, json }); // truyền response làm baseline
});
Pitfall 2 — Patch Field Không Tồn Tại → undefined
Gán vào nested field khi parent object chưa tồn tại → TypeError hoặc tạo field sai chỗ trong schema.
// SAI — nếu json.featureFlags là undefined, lỗi TypeError
await page.route('**/api/user', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.featureFlags.newDashboard = true; // TypeError nếu featureFlags undefined
await route.fulfill({ response, json });
});
// ĐÚNG — guard trước khi access nested
await page.route('**/api/user', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.featureFlags = {
...(json.featureFlags ?? {}), // safe spread nếu undefined
newDashboard: true,
};
await route.fulfill({ response, json });
});
Pitfall 3 — Backend Down Không Có Fallback
Khi backend không accessible, route.fetch() throw error → test crash với message không rõ ràng. Cần bắt lỗi và có fallback hành vi rõ ràng.
// RISKY — không có xử lý khi fetch fail
await page.route('**/api/user', async (route) => {
const response = await route.fetch(); // throw nếu backend down
const json = await response.json();
json.role = 'admin';
await route.fulfill({ response, json });
});
// TỐT HƠN — fallback về full mock khi backend không accessible
await page.route('**/api/user', async (route) => {
try {
const response = await route.fetch();
const json = await response.json();
json.role = 'admin';
await route.fulfill({ response, json });
} catch {
// Fallback mock khi backend không có
await route.fulfill({
status: 200,
json: { id: 1, name: 'Fallback User', role: 'admin' },
});
}
});
Pitfall 4 — Mutate Nested Object Gây Side-Effect
JavaScript object là reference — mutate trực tiếp nested object có thể ảnh hưởng không mong muốn nếu object được tái sử dụng trong cùng handler.
// RISKY — mutate trực tiếp nested object
await page.route('**/api/user', async (route) => {
const response = await route.fetch();
const json = await response.json();
// Mutate trực tiếp — nếu json.settings được dùng lại ở đây sau đó
// có thể thấy giá trị đã bị thay
json.settings.theme = 'dark';
json.settings.language = 'vi';
await route.fulfill({ response, json });
});
// AN TOÀN HƠN — tạo object mới cho nested
await page.route('**/api/user', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.settings = {
...json.settings, // spread tạo shallow copy
theme: 'dark',
language: 'vi',
};
await route.fulfill({ response, json });
});
Với nested object nhiều tầng, shallow spread chỉ an toàn tại tầng trực tiếp. Nếu cần patch sâu hơn, tạo copy tại tầng cần patch.
Tổng Kết
- Pattern modify real:
route.fetch()→ parse JSON → patch field →route.fulfill({ response, json }). - Khác full mock: modify real giữ phần lớn data thật, chỉ override field cần thiết; full mock soạn toàn bộ data hardcode.
fulfill({ response, json }):responselàm baseline (status + headers),jsonoverride body.- Nếu override headers, phải spread
...response.headers()trước rồi thêm field — không spread → mất toàn bộ headers thật. - Use case: test subscription tier/feature flag/A/B variant mà không seed DB; inject empty state, error field, missing field.
- Patch array dùng
.map()với spread object — không mutate trực tiếp item trong array. - Conditional modify: guard
json.field && json.field.length > 0trước khi patch nested. - Limitation: backend phải accessible; latency backend ảnh hưởng tốc độ test; schema thay đổi cần review patch logic; POST/PUT/DELETE có side-effect thật — cân nhắc dùng full mock.
- 4 pitfall: quên
responsetrong fulfill (mất status/headers), patch field không tồn tại (TypeError), backend down không có fallback, mutate nested reference (side-effect).
Quiz 5 Câu
Câu 1
Handler sau có gì sai?
await page.route('**/api/user', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.subscription = 'premium';
await route.fulfill({ json });
});
- Thiếu
awaittrướcroute.fulfill. - Thiếu
responsetrongfulfill({ json })— status và headers gốc từ backend bị bỏ mất. Playwright dùng status 200 mặc định và headers tối thiểu. Nếu app phụ thuộcset-cookiehay custom header, behavior sai. Sửa:await route.fulfill({ response, json }). - Không thể patch
subscriptiontrực tiếp lên object. - Cần gọi
response.dispose()trước khi fulfill.
Đáp án
B. fulfill({ json }) không có response → mất toàn bộ status và headers từ backend thật. Playwright fulfill với status 200 mặc định và chỉ set content-type: application/json. Với endpoint trả set-cookie để maintain session, hay custom header x-rate-limit, behavior test sẽ khác production. Pitfall 1 của bài.
Câu 2
Test cần bật feature flag newCheckout: true mà giữ nguyên các flag khác. Cú pháp nào đúng?
json.featureFlags.newCheckout = true;— mutate trực tiếp.json.featureFlags = { newCheckout: true };— thay thế toàn bộ flags.json.featureFlags = { ...json.featureFlags, newCheckout: true };— spread flags hiện có, override field cần patch. Các flag còn lại giữ nguyên. An toàn với undefined vì nếufeatureFlagsundefined sẽ tạo object mới chỉ cónewCheckout.json = { ...json, featureFlags: { newCheckout: true } };
Đáp án
C. Spread ...json.featureFlags giữ lại tất cả flag hiện có, rồi override newCheckout: true. A cũng hoạt động về chức năng nhưng mutate trực tiếp — rủi ro side-effect khi handler phức tạp. B sai — xóa sạch tất cả flag, chỉ còn newCheckout. D sai — object const không thể reassign với =, phải sửa field trực tiếp; đồng thời object featureFlags vẫn chỉ có newCheckout.
Câu 3
Để test empty state của trang orders mà không cần xóa data trong DB, cách nào ngắn nhất và đúng nhất?
- Fetch, parse, gán
json = [], fulfill. const response = await route.fetch(); await route.fulfill({ response, json: [] });— không cần parse body cũ, fulfill thẳng vớijson: []. Status và headers giữ nguyên từ backend.- Dùng
route.abort()để backend trả lỗi. - Dùng
route.continue()với body override.
Đáp án
B. Khi inject empty state, không cần inspect body gốc — bỏ qua bước parse, truyền thẳng json: [] để override body. response làm baseline giữ status 200 và headers. A sai về syntax — json là const từ response.json(), không thể reassign. C sai — abort trả network error, app render error state chứ không phải empty state. D sai — continue không có option override body.
Câu 4
Backend trả { user: { id: 1, settings: { theme: 'light' } } }. Handler patch như sau:
json.user.settings.theme = 'dark';
json.user.settings.notifications = true;
await route.fulfill({ response, json });
Rủi ro nào có thể xảy ra?
- TypeError vì không thể gán vào nested object.
- Mutate trực tiếp
json.user.settings— nếu object này được tham chiếu ở chỗ khác trong handler (vd log, assert), sẽ thấy giá trị đã bị thay. Với handler đơn giản không có reuse, hoạt động bình thường nhưng không theo best practice. An toàn hơn:json.user.settings = { ...json.user.settings, theme: 'dark', notifications: true }. - Playwright không cho phép patch nested object.
- Fulfill sẽ serialize sai vì nested quá sâu.
Đáp án
B. Pitfall 4 của bài. JavaScript là pass-by-reference cho object — mutate nested field thay đổi object gốc. Trong handler đơn giản với một luồng thực thi, thường không gây lỗi visible, nhưng là anti-pattern: nếu sau này handler phức tạp hơn (vd có log json.user.settings trước và sau patch), giá trị "trước" đã bị thay. Dùng spread { ...json.user.settings, theme: 'dark' } tạo object mới rõ ràng hơn về ý định.
Câu 5
Test cần modify response của POST /api/payment (tạo payment thật, không idempotent). Cách nào phù hợp nhất?
- Dùng
route.fetch() + fulfill({ response, json })— backend tạo payment thật, patch fieldstatustrong response. - Dùng full mock
route.fulfill({ json: mockPaymentResponse })— không gọi backend, không tạo payment thật, không có side-effect. An toàn nhất cho endpoint không idempotent. Test chạy nhiều lần không để lại data rác trong DB. - Dùng
route.continue()để backend xử lý. - Cả A và B đều phù hợp như nhau.
Đáp án
B. Limitation và pitfall 4 của bài — route.fetch() với POST không idempotent gọi backend thật, tạo payment thật trong DB test mỗi lần test chạy. Sau 100 lần chạy = 100 payment rác. Full mock route.fulfill({ json: mockResponse }) hoàn toàn tránh side-effect này, tối ưu hơn cho endpoint có side-effect. A kỹ thuật đúng nhưng để lại data rác. C không cho phép modify response. D sai — A và B không phù hợp như nhau về mặt side-effect.
Bài Tiếp Theo
Bài 120: Slow API Simulation — Simulate độ trễ mạng và backend chậm trong test để verify loading state, skeleton UI, timeout handling và user experience khi API không phản hồi nhanh.
