通过拦截 fetch 和 XHR 模拟 API 响应
后端还没好。或者它好了,但你没法让它按命令返回一个空列表、一个 500 错误和三秒延迟。本文介绍如何用一条 JustZix JS 规则,直接在浏览器里拦截 fetch 和 XMLHttpRequest,返回假的 JSON,让你能针对你喜欢的任何场景构建和测试前端。
为什么要在浏览器里拦截
对「我需要假数据」的常规答案是一个 mock 服务器 —— MSW、json-server,或开发环境里的一个桩。它们很好,对一整个团队来说也是正确的选择。但有时你想要更轻量的东西:
- 你在调试生产环境,没法把它指向一个假后端。
- 你想复现某一个特定的边界情况,又不想动项目配置。
- 你在审查别人的网站,根本没有代码仓库访问权。
一条 JustZix JS 规则活在浏览器里,按 URL 模式生效,且不需要改动任何项目。这正是它的定位:快速、精准、用完即弃的 mock。
包装 window.fetch
核心技巧是用你自己的函数替换 window.fetch,让它逐请求地决定是 mock 还是放行。在 document_start 运行这段代码,确保它在任何应用代码调用 fetch 之前就装好了:
// Save the original so we can fall through to the real network
const realFetch = window.fetch.bind(window);
// Your mock table: match by URL substring -> response factory
const mocks = [
{
match: '/api/user/profile',
response: () => jsonResponse({ id: 1, name: 'Ada Lovelace', plan: 'pro' })
},
{
match: '/api/orders',
response: () => jsonResponse([]) // the empty-list edge case
}
];
window.fetch = async (input, init) => {
const url = typeof input === 'string' ? input : input.url;
const hit = mocks.find(m => url.includes(m.match));
if (hit) {
console.log('[mock] intercepted', url);
return hit.response();
}
return realFetch(input, init); // not mocked -> real request
};
构造一个合成的 Response
你的应用期待一个真正的 Response 对象 —— 它会调用 .json()、检查 .ok、读取 .status。Response 构造函数免费给你这一切:
// Helper: a 200 OK JSON response
function jsonResponse(data, status = 200) {
return new Response(JSON.stringify(data), {
status,
headers: { 'Content-Type': 'application/json' }
});
}
因为它是一个货真价实的 Response,那些写 const r = await fetch(...); if (r.ok) return r.json(); 的代码无需修改就能工作。这正是要点 —— 应用分辨不出区别。
模拟延迟
空列表和顺利路径很简单。Bug 藏在慢路径里:永远不消失的转圈、竞态条件、重复提交。在 resolve 之前加一段延迟:
// A small sleep helper
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
// In the mock table, await it before returning
{
match: '/api/search',
response: async () => {
await sleep(3000); // 3 seconds of "loading..."
return jsonResponse({ results: [] });
}
}
模拟错误码
现在轮到不顺利的路径。一个 500、一个 404、一个应该把你弹回登录的 401 —— 用正确的状态码返回它们,让你的错误处理真的跑起来:
// 500 Internal Server Error
{
match: '/api/checkout',
response: () => jsonResponse(
{ error: 'payment_gateway_timeout' },
500
)
}
// A network failure (fetch rejects, not resolves)
{
match: '/api/flaky',
response: () => Promise.reject(new TypeError('Failed to fetch'))
}
注意区别:一个 500 仍然会以 r.ok === false resolve,而一个断开的连接会 reject。两者都测 —— 应用经常处理了其中一个,却在另一个上崩溃。
包装 XMLHttpRequest
大量旧代码,以及像 axios 这样的库,底层仍然用 XMLHttpRequest。fetch 拦截碰不到它们。XHR 伪造起来更笨重,但一个薄薄的包装能覆盖常见情况:
const RealXHR = window.XMLHttpRequest;
window.XMLHttpRequest = function () {
const xhr = new RealXHR();
const realOpen = xhr.open;
let mockedUrl = null;
xhr.open = function (method, url, ...rest) {
if (url.includes('/api/legacy')) mockedUrl = url;
return realOpen.call(this, method, url, ...rest);
};
const realSend = xhr.send;
xhr.send = function (...args) {
if (!mockedUrl) return realSend.apply(this, args);
// Fake a successful response without hitting the network
setTimeout(() => {
Object.defineProperty(xhr, 'readyState', { value: 4 });
Object.defineProperty(xhr, 'status', { value: 200 });
Object.defineProperty(xhr, 'responseText', {
value: JSON.stringify({ legacy: true })
});
xhr.onreadystatechange && xhr.onreadystatechange();
xhr.onload && xhr.onload();
}, 0);
};
return xhr;
};
这是刻意写得极简的 —— 它用 JSON 文本覆盖了 onload / onreadystatechange,这正是 90% 的 XHR 代码所需要的。如果某个库会检查 getAllResponseHeaders(),你还得把那个也打桩。到了那一步,老实说,还是去用一个真正的 mock 服务器吧。
什么时候改用真正的 mock 服务器
浏览器拦截是快速、限定范围、用完即弃的活儿的正确工具。在以下情况切换到 MSW 或 json-server:
- 整个团队需要同样的 mock,并在代码仓库里做版本管理。
- 你需要按请求体匹配、有状态的序列,或录制与回放。
- 你的测试套件(Playwright、Cypress)要在 CI 里跑这些 mock。
经验法则:如果 mock 应该被提交,用服务器。如果它应该在你关掉规则时就消失,用 JustZix。
另见
- 强制指定 A/B 测试变体 —— mock 开关接口来固定一条分支。
- 构建一个价格与库存监视器 —— 观察 DOM 而不是网络。
- 示例区里的可直接粘贴的代码片段。
别再等后端了。安装 JustZix,把一个 fetch 包装放进一条限定范围的 JS 规则,今天就针对每个场景构建前端。
为这篇文章评分
暂无评分 — 成为第一个。