一键导入
msw
MSW (Mock Service Worker) 2.x 最佳實踐指南。當需要在 Vitest 中攔截 HTTP 請求、測試 API 整合(含 Octokit)、模擬錯誤/rate limiting、或組織 mock handlers 時使用。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
MSW (Mock Service Worker) 2.x 最佳實踐指南。當需要在 Vitest 中攔截 HTTP 請求、測試 API 整合(含 Octokit)、模擬錯誤/rate limiting、或組織 mock handlers 時使用。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Zod v4 schema validation 最佳實踐指南。當需要定義 schema、驗證/解析 JSON 資料、type inference、或處理 unknown data 時使用。
Svelte 5 + Astro 整合最佳實踐指南。當需要建立 Svelte 元件、使用 runes API、整合 Astro islands、或用 Testing Library 測試 Svelte 元件時使用。
GitHub GraphQL API 最佳實踐指南。當需要使用 GraphQL 查詢使用者資料、處理 cursor pagination、計算 rate limit、或除錯 GraphQL errors 時使用。
gayanvoice/top-github-users 架構參考指南。當需要了解 GitHub 使用者排行榜的資料抓取管線、國家設定、排行計算邏輯、已知問題、或社群需求時使用。
Commander.js v14 CLI 框架最佳實踐。當需要建立 CLI 工具、解析命令列參數、設計 subcommands 時使用。
GitHub Actions CI/CD 最佳實踐指南。當需要設定 workflow、cron 排程、GitHub Pages 部署、使用 Octokit API、或處理 rate limiting 時使用。
| name | msw |
| description | MSW (Mock Service Worker) 2.x 最佳實踐指南。當需要在 Vitest 中攔截 HTTP 請求、測試 API 整合(含 Octokit)、模擬錯誤/rate limiting、或組織 mock handlers 時使用。 |
npm install -D msw
// src/mocks/handlers.ts
import { http, HttpResponse } from "msw";
export const handlers = [
http.get("https://api.github.com/users/:username", ({ params }) => {
return HttpResponse.json({
login: params.username,
name: "Test User",
followers: 100,
});
}),
];
// src/mocks/server.ts
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);
// vitest.setup.ts
import { beforeAll, afterEach, afterAll } from "vitest";
import { server } from "./src/mocks/server";
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// vitest.config.ts
test: {
setupFiles: ["./vitest.setup.ts"],
}
三個 lifecycle hook 缺一不可:
server.listen() — 啟用攔截server.resetHandlers() — 移除 server.use() 的覆蓋server.close() — 還原原始網路行為import { http, HttpResponse } from "msw";
// GET + path params
http.get("/users/:id", ({ params }) => {
return HttpResponse.json({ id: params.id, name: "Alice" });
});
// POST + request body
http.post("/users", async ({ request }) => {
const body = await request.json();
return HttpResponse.json({ id: 1, ...body }, { status: 201 });
});
// DELETE
http.delete("/users/:id", () => {
return new HttpResponse(null, { status: 204 });
});
// Catch-all
http.all("/analytics/*", () => new HttpResponse(null, { status: 200 }));
不要放在 handler URL 裡,從 request.url 讀取:
http.get("https://api.github.com/search/users", ({ request }) => {
const url = new URL(request.url);
const q = url.searchParams.get("q");
const page = Number(url.searchParams.get("page") || "1");
return HttpResponse.json({ total_count: 1, items: [{ login: "alice" }] });
});
http.post<
{ owner: string; repo: string }, // path params
{ title: string }, // request body
{ id: number; number: number } // response body
>(
"https://api.github.com/repos/:owner/:repo/issues",
async ({ params, request }) => {
const body = await request.json();
return HttpResponse.json({ id: 1, number: 42 });
}
);
HttpResponse.json({ key: "value" }) // application/json
HttpResponse.json({ error: "Not found" }, { status: 404 })
HttpResponse.text("Hello") // text/plain
HttpResponse.html("<p>Hello</p>") // text/html
HttpResponse.xml("<root><id>1</id></root>") // application/xml
HttpResponse.error() // 網路錯誤(連線失敗)
new HttpResponse(null, { status: 204 }) // 自訂 status
單一測試的覆蓋,afterEach 的 resetHandlers() 會自動移除:
it("handles server error", async () => {
server.use(
http.get("/api/data", () => {
return HttpResponse.json({ error: "Internal" }, { status: 500 });
})
);
// ... 測試錯誤路徑
});
it("handles network failure", async () => {
server.use(
http.get("/api/data", () => HttpResponse.error())
);
});
server.use(
http.get("/api/data", () => HttpResponse.json({ first: true }), { once: true }),
http.get("/api/data", () => HttpResponse.json({ second: true })),
);
// HTTP 錯誤
http.get("/api", () => HttpResponse.json({ message: "Forbidden" }, { status: 403 }));
// 網路錯誤
http.get("/api", () => HttpResponse.error());
// Timeout
import { delay } from "msw";
http.get("/api", async () => {
await delay(30_000);
return HttpResponse.json({ data: "late" });
});
// Rate limiting
http.get("/api", () => {
return HttpResponse.json(
{ message: "Rate limit exceeded" },
{ status: 429, headers: { "Retry-After": "60" } }
);
});
Octokit 發 HTTP 到 https://api.github.com,MSW 直接攔截:
import { http, HttpResponse } from "msw";
export const githubHandlers = [
// Search users
http.get("https://api.github.com/search/users", ({ request }) => {
const url = new URL(request.url);
return HttpResponse.json({
total_count: 1,
incomplete_results: false,
items: [{ login: "alice", id: 1, avatar_url: "https://..." }],
});
}),
// Get user profile
http.get("https://api.github.com/users/:username", ({ params }) => {
return HttpResponse.json({
login: params.username,
name: "Alice",
followers: 100,
company: "ACME",
location: "Taipei",
});
}),
// GraphQL (contribution data)
http.post("https://api.github.com/graphql", async ({ request }) => {
return HttpResponse.json({
data: {
user: {
contributionsCollection: {
contributionCalendar: { totalContributions: 365 },
},
},
},
});
}),
// Rate limit
http.get("https://api.github.com/rate_limit", () => {
return HttpResponse.json({
rate: { remaining: 4999, reset: Math.floor(Date.now() / 1000) + 3600 },
});
}),
];
// test
import { Octokit } from "@octokit/rest";
const octokit = new Octokit({ auth: "fake-token" });
it("searches users", async () => {
const { data } = await octokit.rest.search.users({ q: "location:Taiwan" });
expect(data.items).toHaveLength(1);
expect(data.items[0].login).toBe("alice");
});
msw-fetch-mock 提供 Undici 風格的 chainable fetch mock API,建構在 MSW 之上。
何時使用:
undici.MockAgent 遷移fetchMock.calls、lastCall())MSW 2.x 原生已足夠,msw-fetch-mock 是可選的便利工具。
src/mocks/
handlers/
github.ts # GitHub API handlers
auth.ts # Auth handlers
index.ts # Re-exports all handlers
server.ts # setupServer(...allHandlers)
// src/mocks/handlers/index.ts
import { githubHandlers } from "./github";
export const handlers = [...githubHandlers];
原則:base handlers 覆蓋 happy path,server.use() 只用於測試特定覆蓋(錯誤、edge case)。
resetHandlers() — server.use() 覆蓋洩漏到後續測試new URL(request.url).searchParamsrequest.json() — body 方法都是 asyncmsw import setupServer — 必須從 msw/node importonUnhandledRequest: "error" — 預設靜默通過,設為 error 及早發現問題server.listen() — 只在 setup file 呼叫一次{ once: true } — 需要堆疊 handler