mswjs:在浏览器和 Node 环境中轻松做 API Mocking
mswjs 是一个同时兼容浏览器和 Node 的 API mocking 库,帮助开发者在 API 还没 ready 的情况下,通过 mock 数据进行完美开发。
mswjs 原理是使用 Service Worker API 拦截(网络层)实际请求实现 API 的请求、响应模拟。
官网地址:mswjs.io/
下面我们会创建一个 Next.js 项目,来演示 mwsjs 的使用。
创建项目 & 安装依赖
npx create-next-app mws-app
√ Would you like to use TypeScript with this project? ... No / Yes
√ Would you like to use ESLint with this project? ... No / Yes
√ Would you like to use Tailwind CSS with this project? ... No / Yes
√ Would you like to use `src/` directory with this project? ... No / Yes
√ Use App Router (recommended)? ... No / Yes
√ Would you like to customize the default import alias? ... No / Yes
Creating a new Next.js app in D:\learning\mws-app.
cd mws-app
pnpm install mws --save-dev
pnpm run dev
> next dev
- ready started server on 0.0.0.0:3000, url: http://localhost:3000
定义 mocks
按照约定,我们将 mock 定义放在 src/mocks
目录中:
mkdir mocks
# 创建 handlers.js 文件
touch src/mocks/handlers.js
Mocking API
mwsjs 支持两种类型的 API Mock,一个是 REST API,一个是 GraphQL API。如果你知道两者的区别,或者压根就不知道 GraphQL,也没有关系,无脑看成 REST API 就行,因为我们 99.9% 的项目都是用 GraphQL。
我们编辑 handlers.js
:
// src/mocks/handlers.js
import { rest } from 'msw'
rest
就是一个服务器(类似 express()
),用于处理请求和响应,我们下面分别介绍。
Request handler
假设我们要做一个基础的登录功能。需要准备两个接口:
POST /login
:登录使用GET /user
:现在登录用户信息
先创建请求处理器 Request handler,使用 rest[METHOD]
方式:
// src/mocks/handlers.js
import { rest } from 'msw'
export const handlers = [
// Handles a POST /login request
rest.post('https://example.com/login', null),
// Handles a GET /user request
rest.get('/user', null),
]
handlers.js
文件是通过一个叫 handlers
的命名导出暴露拦截的/要 Mock 的请求的。
Response resolver
前一步已经做到请求拦截了,这一步我们在 Mock 响应数据:
// src/mocks/handlers.js
import { rest } from 'msw'
export const handlers = [
rest.post('https://example.com/login', (req, res, ctx) => {
// Persist user's authentication in the session
sessionStorage.setItem('is-authenticated', 'true')
return res(
// Respond with a 200 status code
ctx.status(200),
)
}),
rest.get('/user', (req, res, ctx) => {
// Check if the user is authenticated in this session
const isAuthenticated = sessionStorage.getItem('is-authenticated')
if (!isAuthenticated) {
// If not authenticated, respond with a 403 error
return res(
ctx.status(403),
ctx.json({
errorMessage: 'Not authorized',
}),
)
}
// If authenticated, return a mocked user details
return res(
ctx.status(200),
ctx.json({
username: 'admin',
}),
)
}),
]
响应回调函数接受 3 个参数:
req
:匹配请求的信息res
:用于创建模拟响应的功能函数ctx
:包含一系列用于设置模拟响应的状态码、头部、正文等功能的函数
整合
当然,上面这些约定 Mocking API 写好之后,并不会自动生效,我们还需要整合到现有项目中。
当然,这块我们根据浏览器端、Node 端分别介绍。
浏览器端
准备
Mock Service Worker 在客户端是通过 Service Worker 做请求拦截的,因此这块我们要单独启动个服务器服务 来自 mwsjs Service Worker 文件中的代码请求 。
这块代码不需要我们手写,直接使用 npx msw init <PUBLIC_DIR> --save
指令就可以了。这里的 <PUBLIC_DIR>
就是指项目的静态资源的存放目录,对 Next.js 来说,就是项目跟路径下的 public/
目录:
npx msw init public/ --save
执行完成后,会在 public/
下,看到多出一个 mockServiceWorker.js
文件。**mockServiceWorker.js**
** 不需要我们做任何修改,放在这里就行了。**这个 worker 脚本会被注入到浏览器网页中,用于拦截请求。
注意,这里的 --save
是必须的,这个选项会在 package.json
文件中写一个 msw
字段。
{
// ...
"msw": {
"workerDirectory": "public"
}
}
这样日后更新 msw 包的时候,public/
目录下的 worker 脚本会自动更新。
配置 worker
接下来在创建 src/mocks/browser.js
文件:
import { setupWorker } from 'msw'
import { handlers } from './handlers'
// This configures a Service Worker with the given request handlers.
export const worker = setupWorker(...handlers)
这一步将我们定义的 handlers 绑定到 mws 上(这时候定义的 Mock API 才与 mws 有了联系)。
我们导出 worker
实例。
启动 worker
worker
实例准备好后,就要在合适的时机启动 worker 了(worker.start()
):
对 Next.js 项目而言,我们在 pages/_app.tsx
中引入:
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
async function initMocks() {
if (typeof window !== 'undefined') {
const { worker } = await import('../mocks/browser')
worker.start()
}
}
initMocks()
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
我们只在开发环境下才进行 API Mock。现在,localhost:3000 打开网页,看见下面的打印结果就说明接入成功了。
然后添加按钮,模拟登录:
// pages/_app.tsx
export default function Home() {
function handleLogin() {
fetch('https://example.com/login', { method: 'post' }).then(res => res.statusText).then(console.log)
}
function handleGetUser() {
fetch('/user').then(res => res.json()).then(console.log)
}
return (
<p>
<button onClick={handleLogin}>Login</button>
<button onClick={handleGetUser}>Get User</button>
</p>
)
}
发现接口 Mock 成功了!
Node 端
配置服务器
创建 src/mocks/server.js
文件:
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
// This configures a request mocking server with the given request handlers.
export const server = setupServer(...handlers)
setupServer()
使用之前定义好的 handlers
创建一个服务器。
需要注意的是,非 DOM 环境下的请求 URL 都必须使用绝对地址(absolute request URLs)。
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(
// NOT "/user", nothing to be relative to!
rest.get('https://api.backend.dev/user', (req, res, ctx) => {
return res(ctx.json({ firstName: 'John' }))
}),
...handlers
)
在我们创建的 Next.js 项目中,默认会有一个 src/pages/api/hello.ts
的 API 路由。我们可以利用它来作为我们测试后端接口请求的方式:
// src/pages/api/hello.ts
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import https from 'node:https'
import type { NextApiRequest, NextApiResponse } from 'next'
type Data = {
name: string
}
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const request = https.request('https://api.backend.dev/user', (response) => {
let data = ''
response.on('data', (chunk) => {
data = data + chunk.toString()
});
response.on('end', () => {
const body = JSON.parse(data)
res.status(200).json(body)
});
})
request.end()
}
或者借助 axios:
pnpm install axios
// src/pages/api/hello.ts
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next'
import axios from 'axios'
type Data = {
name: string
}
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
axios.get('https://api.backend.dev/user')
.then(response => res.status(200).json(response.data))
}
注意,不要使用 Fetch API!mws 是通过拦截 Node 的
http
/https
module 实现 API Mocking 的,因此 Node 中原生支持的 fetch API 会 Mock 失败的。
启动服务器
在 pages/_app.tsx
中引入:
// pages/_app.tsx
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
async function initMocks() {
if (typeof window !== 'undefined') {
const { worker } = await import('../mocks/browser')
worker.start()
} else {
const { server } = await import('../mocks/server')
server.listen()
}
}
initMocks()
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
浏览器访问 http://localhost:3000/api/hello 就能看到正确的 Mock 数据了。
区分 dev/prod 环境
目前我们的代码在开发(development,对应 next dev
)和生产环境(production,对应 next start
)都会启用 API Mocking,这不是我们想要的。因此,我们需要控制只在开发环境进行 API Mocking,这里可以借助 Next.js 的环境文件 的支持。
首先,在项目根目录下创建两个文件:.env.development
和 .env.production
:
NEXT_PUBLIC_API_MOCKING=enabled
NEXT_PUBLIC_API_MOCKING=disabled
Next.js 会将 NEXT_PUBLIC_
做前缀的环境变量,在浏览器和 Node 环境中都设置。如上所示,开发环境我们启用 mwsjs,生产环境则禁用 mwsjs。
接下来调整一下 pages/_app.tsx
中的代码,判断仅在 API Mocking 启用的情况下,引入 mwsjs 配置。
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
async function initMocks() {
if (typeof window !== 'undefined') {
const { worker } = await import('../mocks/browser')
worker.start()
} else {
const { server } = await import('../mocks/server')
server.listen()
}
}
if (process.env.NEXT_PUBLIC_API_MOCKING === 'enabled') {
initMocks()
}
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
当然,如果感觉 _app.tsx
中的代码很多,也可以将 initMocks
函数放到 mocks/index.js
文件中:
async function initMocks() {
if (typeof window !== 'undefined') {
const { worker } = await import('./browser')
worker.start()
} else {
const { server } = await import('./server')
server.listen()
}
}
initMocks()
然后修改 pages/_app.tsx
文件:
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
if (process.env.NEXT_PUBLIC_API_MOCKING === 'enabled') {
require('../mocks')
}
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
还是一样的效果。
好了,关于 mwsjs 的介绍就到这里啦,感谢各位的阅读!
参考链接
转载自:https://juejin.cn/post/7250177390278197304