原文发表在我的博客 doma.land/blog/msw-ha…
当我们使用 Mock Service Worker (MSW) 进行 API Mocking 时,随着接口数量的增长,我们常会遇到这些问题:
其一,REST API 常常同一个路径支持多个请求方法,我们需要重复书写相同的 API 路径。
// mocks/handlers.js
const handlers = [
rest.get("/api/todos", "..."),
rest.post("/api/todos", "..."),
rest.get("/api/todos/:id", "..."),
rest.put("/api/todos/:id", "..."),
rest.delete("/api/todos/:id", "..."),
// 如果路径有嵌套,则会更糟
rest.get("/api/todos/:id/attachments", "..."),
rest.post("/api/todos/:id/attachments", "..."),
rest.get("/api/todos/:id/attachments/:name", "..."),
rest.put("/api/todos/:id/attachments/:name", "..."),
rest.delete("/api/todos/:id/attachments/:name", "..."),
]
其二,将这么多 Mock 接口定义在同一个文件中很笨重,因此我们往往会将它们拆分到多个模块中。 通常,我们会根据业务功能来拆分模块,而对这些模块的命名常常恰与 REST API 的路径是重复的。
作为程序员,我们既不喜欢命名,也不喜欢重复。
那么,有没有一种方法,可以直接通过模块文件的路径来拼接出 API 路径,就像 Next.js 的 API Routes 那样?例如,能否创建一个 api/todos.js
文件并导出一个 POST
、GET
常量,然后自动为我们生成 POST /api/todos
和 GET /api/todos
两个 MSW handlers?
// mocks/api/todos.js
export const GET = (req, res, { json }) => res(json("..."));
export const POST = (req, res, { status, json }) => res(status(201), json("..."));
幸运的是,如果你使用 webpack 进行构建,你可以通过 require.context
做到这一点。
require.context
允许我们动态地去 require
一个目录下的各个文件,例如:
// 搜索 ./api 目录下所有以 ".js" 结尾的文件(包括子目录下的文件)
require.context("./api", true, /\.js$/)
这一特性使得我们可以实现类似 API Routes 的功能:
// mocks/handlers.js
const requireMockHandler = require.context("./api", true, /\.js$/)
export const handlers = requireMockHandler
.keys()
.reduce((allHandlers, path) => {
const handlers = requireMockHandler(path)
// 将 require.context 读取到的文件路径 "./path/[to]/handlers.js"
// 转换为 MSW 接收的 API 路径 "/api/path/:to/handlers"
const apiPath =
"/api/" +
path
// 移除开头的 "./"
.replace(/^\.\//, "")
// 移除结尾的 ".js"
.replace(/\.[^/.]+$/, "")
// 将方括号标记的变量 "[param]" 改为冒号标记 ":param"
.replace(/\[([^/]*)\]/g, ":$1")
const pathHandlers = Object.getOwnPropertyNames(handlers).reduce((moduleHandlers, exportName) => {
const handler = handlers[exportName]
const method = exportName.toLowerCase()
// 对于模块中导出的每个字段,注册一个 msw handler
if (method in rest) {
moduleHandlers.push(rest[method](apiPath, handler))
}
return moduleHandlers
}, [])
return [...allHandlers, ...pathHandlers]
}, [])
最终,我们只需要根据 API 路径在 ./api
目录下创建文件即可获得所需的 msw handlers,而不再需要反复在代码中书写 API 路径。
src/mocks/
├── api/
| ├── todos.js
| └── todos/
| ├── [id].js
| └── [id]/
| ├── attachments.js
| └── attachments/
| └── [name].js
└── handlers.js
文中展示的示例代码片段可在此仓库中找到 github.com/SevenOutman…