msw-fetch-mock
v0.4.13
Published
Undici-style fetch mock API built on MSW (Mock Service Worker)
Downloads
2,471
Maintainers
Readme
msw-fetch-mock
基於 MSW(Mock Service Worker)的 Undici 風格 fetch mock API。
如果你熟悉 Cloudflare Workers 的 fetchMock(來自 cloudflare:test)或 Node.js undici 的 MockAgent,你已經會使用這個 API 了。
透過 subpath exports 支援 Node.js(msw/node)、瀏覽器(msw/browser)和原生(無 MSW 依賴)環境。
系統需求
- Node.js >= 18
- MSW ^1.0.0(透過
/legacy)或 ^2.12.7 — 使用/native時可選
安裝
npm install -D msw-fetch-mock mswmsw 是 peer dependency — 需要自行安裝你的版本。
若不需要 MSW(直接 patch globalThis.fetch):
npm install -D msw-fetch-mock快速開始
Node.js(Vitest、Jest)
// 可使用根路徑匯入或明確的 /node 子路徑
import { fetchMock } from 'msw-fetch-mock';
// import { fetchMock } from 'msw-fetch-mock/node';
beforeAll(() => fetchMock.activate({ onUnhandledRequest: 'error' }));
afterAll(() => fetchMock.deactivate());
afterEach(() => {
fetchMock.assertNoPendingInterceptors();
fetchMock.reset();
});
it('模擬 GET 請求', async () => {
fetchMock
.get('https://api.example.com')
.intercept({ path: '/users', method: 'GET' })
.reply(200, { users: [{ id: '1', name: 'Alice' }] });
const res = await fetch('https://api.example.com/users');
const data = await res.json();
expect(data.users).toHaveLength(1);
});瀏覽器(Storybook、Vitest Browser Mode)
import { setupWorker } from 'msw/browser';
import { createFetchMock } from 'msw-fetch-mock/browser';
const worker = setupWorker();
const fetchMock = createFetchMock(worker);
beforeAll(async () => {
await fetchMock.activate({ onUnhandledRequest: 'error' });
});
afterAll(() => fetchMock.deactivate());
afterEach(() => {
fetchMock.assertNoPendingInterceptors();
fetchMock.reset();
});搭配現有的 MSW server
如果你已經在使用 MSW,可以傳入你的 server 來共用同一個攔截器:
import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';
import { createFetchMock } from 'msw-fetch-mock';
const server = setupServer(http.get('/api/users', () => HttpResponse.json([{ id: 1 }])));
const fetchMock = createFetchMock(server);
beforeAll(() => server.listen());
afterAll(() => server.close());
afterEach(() => {
server.resetHandlers();
fetchMock.assertNoPendingInterceptors();
});注意: 同一時間只能有一個 MSW server 處於啟動狀態。如果已有 server 在監聽中,獨立模式的
activate()會拋出錯誤。請使用createFetchMock(server)來共用現有的 server。
原生模式(無 MSW 依賴)
若不想安裝 MSW,/native 子路徑會直接 patch globalThis.fetch:
import { fetchMock } from 'msw-fetch-mock/native';
beforeAll(async () => {
await fetchMock.activate({ onUnhandledRequest: 'error' });
});
afterAll(() => fetchMock.deactivate());
afterEach(() => {
fetchMock.assertNoPendingInterceptors();
fetchMock.reset();
});API 完全相同 — 只有底層傳輸方式不同(無 Service Worker、無 MSW server)。
Legacy(MSW v1)
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { createFetchMock } from 'msw-fetch-mock/legacy';
const server = setupServer();
const fetchMock = createFetchMock(rest, server);
beforeAll(() => fetchMock.activate());
afterAll(() => fetchMock.deactivate());
afterEach(() => {
fetchMock.assertNoPendingInterceptors();
fetchMock.reset();
});詳見 MSW v1 Legacy 指南。
未處理的請求
預設情況下 activate() 使用 'error' 模式 — 未匹配的請求會導致 fetch() 拒絕。這包含對已消耗攔截器的請求(一次性攔截器使用後,其 handler 會從 MSW 中移除)。
// 拒絕未匹配的請求(預設行為)
fetchMock.activate();
fetchMock.activate({ onUnhandledRequest: 'error' });
// 記錄警告但允許通過
fetchMock.activate({ onUnhandledRequest: 'warn' });
// 靜默允許通過
fetchMock.activate({ onUnhandledRequest: 'bypass' });
// 自訂回呼
fetchMock.activate({
onUnhandledRequest: (request, print) => {
if (request.url.includes('/health')) return; // 忽略
print.error(); // 阻擋其他所有請求
},
});
enableNetConnect()的優先順序高於onUnhandledRequest— 允許的主機一律直接通過。
API 概覽
匯入路徑
| 路徑 | 環境 | MSW 版本 |
| ------------------------ | ----------------------------- | -------- |
| msw-fetch-mock | Node.js(re-exports /node) | v2 |
| msw-fetch-mock/node | Node.js | v2 |
| msw-fetch-mock/browser | 瀏覽器 | v2 |
| msw-fetch-mock/native | 任何環境(無 MSW) | 不需要 |
| msw-fetch-mock/legacy | Node.js(MSW v1) | v1 |
fetchMock(單例)
預先建立的 FetchMock 實例,適用於獨立使用。匯入後直接呼叫 activate() 即可 — 無需額外設定。
可從 msw-fetch-mock 和 msw-fetch-mock/node 匯入。
createFetchMock(server?) / createFetchMock(worker)
建立 FetchMock 的工廠函式,會自動搭配對應的 adapter。
- Node:
createFetchMock(server?)— 可選擇性傳入現有的 MSWSetupServer - 瀏覽器:
createFetchMock(worker)— 傳入 MSWSetupWorker(必要) - 原生:
createFetchMock()— 無需參數,無 MSW 依賴 - Legacy:
createFetchMock(rest, server?)— 傳入 MSW v1 的rest物件
new FetchMock(adapter?)
使用明確的 MswAdapter 建立 FetchMock 實例。可使用 NodeMswAdapter、BrowserMswAdapter 或 NativeFetchAdapter。
攔截與回應
fetchMock
.get(origin) // string、RegExp 或 function
.intercept({ path, method, headers, body, query }) // 匹配條件
.reply(status, body, options) // 定義回應
.times(n) / .persist(); // 重複控制呼叫歷史
fetchMock.calls.lastCall(); // 最近一次
fetchMock.calls.firstCall(); // 最早一次
fetchMock.calls.nthCall(2); // 第 2 次呼叫(1-indexed)
fetchMock.calls.filterCalls({ method: 'POST', path: '/users' }, { operator: 'AND' });斷言與清理
fetchMock.assertNoPendingInterceptors(); // 若有未消耗的攔截器則拋出錯誤
fetchMock.reset(); // 清除攔截器 + 呼叫歷史 + handlers測試環境
每次 CI 推送都會在以下環境執行 E2E 測試:
| 環境 | 模組系統 | 測試框架 | | -------------- | ------------- | ------------------- | | Jest ESM | ESM (import) | Jest | | Jest CJS | CJS (require) | Jest | | Node.js Test | ESM (import) | Node test runner | | Node.js CJS | CJS (require) | Node test runner | | Native ESM | ESM (import) | Node test runner | | Native CJS | CJS (require) | Node test runner | | Legacy CJS | CJS (require) | Jest (MSW v1) | | Vitest Browser | ESM (import) | Vitest + Playwright |
文件
- API 參考 — 完整的 API 細節、匹配選項、回應回呼
- Cloudflare Workers 遷移指南 — 從
cloudflare:testfetchMock 遷移 - MSW v1 Legacy 指南 — 搭配 MSW v1 使用 msw-fetch-mock
開發
pnpm install
pnpm build # 使用 tsup 建置
pnpm test # 單元測試(vitest)
pnpm test:e2e # E2E 測試(jest-esm、jest-cjs、node-test、node-cjs、legacy-cjs)E2E 測試
E2E 測試驗證已發佈的套件能在不同 runtime 和模組系統下正常運作。腳本會建置、透過 npm pack 打包 tarball,然後安裝到每個 e2e/ 專案中 — 完全模擬 CI 流程。
# 執行預設套組(跳過 vitest-browser)
pnpm test:e2e
# 執行單一套組
pnpm test:e2e -- node-cjs
# 執行所有套組,包含 vitest-browser(會自動安裝 Playwright)
pnpm test:e2e -- --all可用套組:jest-esm、jest-cjs、node-test、node-cjs、native-esm、native-cjs、legacy-cjs、vitest-browser
