浏览器端 Mock 工具 - Mock Service Worker

mswjs.io/

Mock Service Worker 是一个 API 模拟库,它使用 Service Worker API 来拦截实际请求。

请求流程图

该库注册了一个 Service Worker,通过 fetch 事件监听应用程序的出站请求,将这些请求指向客户端库,并发送模拟响应(如果有)给工作线程以便进行响应。

浏览器端 Mocking REST API 使用流程:

  1. 安装npm i msw -D
  2. 通过 Mock Service Worker 提供的 CLI 来注册 service worker
sh 复制代码
npx msw init <PUBLIC_DIR> --save
// <PUBLIC_DIR> 为公共目录

// 如果是 Create React App 用 public/ 代替 <PUBLIC_DIR>
npx msw init public/ --save
  1. 定义请求处理程序
ts 复制代码
// src/mocks/handler.ts
import { rest } from 'msw';

export const handlers = [
	rest.get('/test', async (req, res, ctx) => {
		return res(ctx.delay(3000), ctx.status(200), ctx.json({ data: 'ok' }));
	}),
];
  1. setupWorker 生成一个 service worker 实例;
ts 复制代码
// src/mocks/browser.ts
import { setupWorker } from 'msw';
import { handlers } from './handler';

const getHandler = () => {
  /** 此处要使用项目中实际的 baseUrl */
	const baseURL = 'http://127.0.0.1';
	/** 处理,给每个路径加上 baseUrl */
	handlers.forEach((item: any) => {
		const fullPath = baseURL + item.info.path;
		item.info.header = `${item.info.method} ${fullPath}`;
		item.info.path = fullPath;
	});

	return handlers;
};

export const worker = setupWorker(...getHandler());
  1. 判断环境,在开发环境中激活 service worker(此时是全局拦截,任何 fetch 请求都会被拦截)
tsx 复制代码
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

if (process.env.NODE_ENV === 'development') {
  const { worker } = require('./mocks/browser')
  worker.start({
    quiet: false, // 是否禁止在控制台打印匹配的日志记录
    onUnhandledRequest: "bypass", // 对于没有 mock 的接口直接通过,原样执行
  })
}


ReactDOM.render(<App />, document.getElementById('root'))

完成以上步骤,启动项目后控制台有以下提示,说明 MSW 启动成功:

功能特性

条件响应

当响应解析器返回 req.passthrough 调用时,MSW将按原样执行捕获的请求,命中实际端点并返回其响应。

ts 复制代码
rest.get('/user', (req, res, ctx) => {
  const userId = req.url.searchParams.get('userId')

  if (userId === 'abc-123') {
    return res(
      ctx.json({
        firstName: 'John',
        lastName: 'Maverick',
      }),
    )
  }
  /** 返回 req.passthrough,请求会发送到服务器  */
  return req.passthrough()
})
响应补丁

响应补丁是一种技术,当模拟的响应基于实际响应时使用。这种技术在使用现有API并打算为各种目的(例如实验或调试)增强它时可能非常有用。

ts 复制代码
rest.get('https://api.github.com/users/:username', async (req, res, ctx) => {
  /** 发送请求,拿到真实数据,可以在该数据上基础上进行修改再响应 */
  const originalResponse = await ctx.fetch(req)
  const originalResponseData = await originalResponse.json()

  return res(
    ctx.json({
      location: originalResponseData.location,
      firstName: 'Not the real first name',
    }),
  )
}),

将拦截的请求对象传递给 ctx.fetch() 调用以获取原始响应。使用 ctx.fetch() 执行一个绕过任何请求处理程序的请求,因为任何 window.fetch() 调用,即使是在响应解析器内部的调用也会被拦截。

强烈建议在响应解析器中使用ctx.fetch()而不是常规的window.fetch(),因为它可以防止实际请求与MSW模拟定义相匹配。

您可能会有意地使用window.fetch(),例如当您需要执行稍后作为另一个响应解析器的一部分进行模拟的请求时。由常规window.fetch()发出的请求将受到模拟。

延迟响应

当没有明确提供延迟持续时间时,Mock Service Worker会在特定的模拟响应上使用一个随机的真实服务器响应时间。

ts 复制代码
rest.delete('/post/:postId', (req, res, ctx) => {
  return res(
    /** 如果没有参数,会随机模拟一个真实的响应时间 */
    ctx.delay(2000),
    ctx.json({
      message: `Post ${req.params.postId} successfully deleted!`,
    }),
  )
})
二进制响应类型

提供一个 BufferSource 对象给 ctx.body() 实用函数,将使用该缓冲区作为模拟响应的主体。二进制数据的支持允许在模拟响应中发送任何类型的媒体内容(图像、音频、文档)

ts 复制代码
import { setupWorker, rest } from 'msw'
import base64Image from '!url-loader!../fixtures/image.jpg'

const worker = setupWorker(
  rest.get('/images/:imageId', async (_, res, ctx) => {
    // Convert "base64" image to "ArrayBuffer".
    const imageBuffer = await fetch(base64Image).then((res) =>
      res.arrayBuffer(),
    )

    return res(
      ctx.set('Content-Length', imageBuffer.byteLength.toString()),
      ctx.set('Content-Type', 'image/jpeg'),
      // Respond with the "ArrayBuffer".
      ctx.body(imageBuffer),
    )
  }),
)

worker.start()
从 post 请求的 body 中获取参数
ts 复制代码
type IReqType = RestRequest<DefaultBodyType, PathParams<string>>;
type IResType = ResponseComposition<DefaultBodyType>;
type ICtxType = RestContext;

export const handlers = [
  rest.post("/login", async (req: IReqType, res: IResType, ctx: ICtxType) => {
    // 从 post 请求的 body 中获取参数(注意:req.body 已废弃)
    const { username, password } = await req.json(); 
  }
]
相关推荐
zqx_714 分钟前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己31 分钟前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称1 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色1 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2341 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河2 小时前
CSS总结
前端·css
BigYe程普2 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H2 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍2 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai2 小时前
网站开发的发展(后端路由/前后端分离/前端路由)
前端