使用Mock Service Worker

使用Mock Service Worker

目录

1. 引言

随着前端应用程序的复杂性不断增加,开发者在开发和测试阶段需要频繁与后端 API 进行交互。然而,在实际的开发过程中,后端服务可能并不总是处于可用状态,或者其开发进度与前端不同步。这种情况可能会导致前端开发进度的滞后。为了应对这一情况,模拟 API 请求的工具应运而生。

Mock Service Worker (MSW) 是一种基于 Service Worker API 的强大工具,专门用于在前端应用程序中拦截和模拟网络请求。通过使用 MSW,开发者可以轻松地模拟后端服务的各种行为,例如响应延迟错误状态码等,确保在不依赖真实后端服务的情况下,前端开发和测试工作能够顺利进行。

2. 简介

什么是 MSW

Mock Service Worker (MSW) 是一个用于拦截和模拟网络请求的 JavaScript 库。MSW 通过利用浏览器中的 Service Worker API 拦截应用程序的 HTTP 请求,并根据预先定义的规则返回模拟响应。这种方法不仅可以在开发过程中提高工作效率,还可以在测试中确保前端代码的稳定性和可靠性。

注意:Service Worker 只能在浏览器环境中工作。在 Node.js 环境中,MSW 利用 Node.js 的请求拦截器库,并允许重用来自浏览器环境的相同模拟定义。

核心功能

  • 拦截并模拟 HTTP 请求:MSW 可以捕获应用程序发出的所有 HTTP 请求,并根据开发者定义的处理程序返回自定义的响应。
  • 支持 REST 和 GraphQL API:无论是传统的 RESTful 接口还是更现代的 GraphQL 查询,MSW 都能够轻松支持并模拟这些请求。
  • 无缝集成到前端开发和测试流程中:MSW 兼容各种前端开发工具和测试框架,确保在各个阶段都能方便地使用它进行模拟。

3. 为什么选择 MSW

在现代前端开发中,选择适当的工具来模拟 API 请求是确保开发流程顺畅的关键。与传统的 mock 工具相比,MSW 具有以下显著优势:

  • 与代码无关:MSW 不依赖于具体的代码实现,它在浏览器的网络层拦截请求。这意味着你可以在不修改应用逻辑的情况下使用 MSW。
  • 支持多种环境:MSW 可以在开发环境、测试环境,甚至生产环境中使用,帮助模拟不同场景下的 API 行为,确保环境的一致性。
  • 动态响应:MSW 允许开发者根据不同的请求动态生成响应,从而使测试更加灵活和全面。
  • 对现代开发工具的良好兼容:MSW 支持 Service Worker API,并与现代 JavaScript 模块系统和工具链(如 Webpack、Rollup、Vite 等)良好集成。

与类似工具的比较

¹ 虽然可以处理 GraphQL 请求,但需要额外设置,并非一流支持。
² JSON Server 本身不在浏览器中运行,但可以从浏览器中请求。

工作原理

Mock Service Worker (MSW) 是一种用于模拟前端应用网络请求的工具,基于浏览器的 Service Worker API。其工作原理如下:

  1. 注册 Service Worker:应用首先注册一个 Service Worker,它作为应用与网络之间的代理,拦截所有 HTTP/HTTPS 请求。

  2. 请求拦截:当应用发出网络请求时,Service Worker 可以捕获并拦截这些请求。

  3. 模拟响应:MSW 使用开发者定义的处理器,根据请求生成相应的模拟响应。

  4. 返回模拟响应:生成的响应会返回给应用程序,就像来自真实服务器一样。

    Node.js 支持:MSW 也可以在 Node环境中拦截并模拟网络请求,适用于服务器端渲染和测试。

总结来说,MSW 的工作原理是通过浏览器的 Service Worker API 在网络请求级别进行拦截和模拟。这种方法使得模拟的过程与应用程序代码完全解耦,并且允许开发者控制网络行为,从而提高了前端开发和测试的灵活性和效率。

4. 实践 MSW

安装和配置

要开始使用 Mock Service Worker,首先需要在项目中安装它。你可以通过 npm 或 yarn 安装:

msw有1.x版本和2.x版本,如果项目node版本 < 18,则需要使用1.x版本,本例子演示1.x版本的使用

npm install msw@1.3.2 --save-dev

或者

yarn add msw@1.3.2 --dev

安装完成后,需要初始化 MSW 并创建一个 Service Worker 文件来操作客户端负责请求拦截。然而,我们不必自己编写任何worker的代码,而是复制库分发的worker文件。 Mock Service Worker 提供了专用的 CLI 来帮助我们做到这一点。

npx msw init public/ --save

然后在项目的src目录下创建一个mocks 目录,并在其中创建一个名为 handlers.ts 的文件来定义模拟的路由和响应逻辑。

// src/mocks/handlers.ts
import { rest } from 'msw';

export const handlers = [
  rest.get('/api/user', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json({ username: 'John Doe' })
    );
  }),
];

接下来,在同一目录下创建一个名为 browser.ts 的文件来设置并启动 Service Worker。

// src/mocks/browser.ts
import { setupWorker } from 'msw';
import { handlers } from './handlers';

// 使用上面定义的处理器初始化 MSW Service Worker
export const worker = setupWorker(...handlers);

集成 MSW 到开发环境中

为了在开发环境中使用 MSW,你需要在应用程序的入口文件(如 main.ts)中启动它。你可以根据当前的环境条件来决定是否启动 MSW,例如在开发环境中启动,在生产环境中则不启动:

// src/main.ts
import { worker } from './mocks/browser'

// ...
  if (process.env.NODE_ENV === 'development') {
    worker.start()
  }
// ...

setupApp()

MSW 的一个重要特点是在开发过程中,它可以用于模拟后端 API。开发者可以使用 MSW 拦截应用中的所有 HTTP 请求,并根据需要调整 API 的响应内容,例如模拟延迟返回不同的 HTTP 状态码等。

接下来在handlers.ts 中编写接口拦截的示例,模拟用户的基本登录流程:

// src/mocks/handlers.ts
import { rest } from 'msw'

export const handlers = [
  rest.post('/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'
      })
    )
  })
]

使用技巧

随着项目的复杂性增加,你可能会遇到一些更复杂的请求场景。这时,MSW 的高级功能将派上用场。例如,你可以:

  • 处理复杂的请求场景:使用 MSW,你可以精确控制请求的拦截和响应逻辑,处理诸如多种查询参数、不同路径参数等复杂场景。
  • 模拟不同状态码和错误响应:通过 MSW,模拟各种 HTTP 状态码(如 404、500 等)非常容易,你可以测试前端在遇到这些错误时的处理方式。
  • 利用 MSW 进行负载测试和压力测试:虽然 MSW 主要用于开发和测试阶段的功能测试,但你也可以用它进行初步的负载测试,评估前端应用在高并发请求下的表现。
  • 模拟延迟或文件上传
// src/mocks/handlers.ts
import { rest } from 'msw';

export const handlers = [
  rest.get('/api/user/:userId', (req, res, ctx) => {
    const { userId } = req.params;
    return res(
      ctx.status(200),
      ctx.json({ username: `User ${userId}` })
    );
  }),
  rest.post('/api/login', (req, res, ctx) => {
    const { username, password } = req.body;
    if (username === 'admin' && password === 'admin') {
      return res(ctx.status(200), ctx.json({ token: 'abc123' }));
    }
    return res(ctx.status(403), ctx.json({ error: 'Invalid credentials' }));
  }),
];

而MSW能处理的场景远不仅如此,更多的功能可以移步官网查看手册;

v2版本:https://mswjs.io/docs/

v1 版本:https://v1.mswjs.io/docs/

5.在Node中实践

BFF作为一个中间层项目,会经常遇到调用其他接口的情况,例如获取oss配置,做统一鉴权等,接下来演示在BFF中的实践,为2.x版本的使用:

添加基本配置

创建src/mocks 文件夹,新增src/mocks/handlers/upload.handlers.ts 和 src/mocks/node.ts 文件:

// src/mocks/handlers/upload.handlers.ts 
import { http, HttpResponse } from 'msw';

interface RequestBody {
  key: string;
}

export const uploadHandlers = [
  // generate_security_token
  http.post("http://rccfile.dev1.rccchina.com/inner/generate_security_token", async ({ request, params }) => {
    const requestBody = await request.json();
    const { key: tokenKey } = requestBody as RequestBody;

    if (!tokenKey) {
      return HttpResponse.json({
        message: "Bad Request",
        code: 400,
        error: "Token key is required.",
      }, { status: 400 });
    }

    if (tokenKey === "empty_response") {
      return HttpResponse.json(null, { status: 200 });
    }

    if (tokenKey === "missing_fields") {
      return HttpResponse.json({
        message: "ok",
        code: 200,
        data: {
          data: {
            AccessKeyId: "mockAccessKeyId",
            SecurityToken: "mockSecurityToken",
            Bucket: "mockBucket",
            Path: "/mock/path",
          }
        },
      }, { status: 200 });
    }

    return HttpResponse.json({
      message: "ok",
      code: 200,
      data: {
        RegionId: "mock-region-id",
        AccessKeyId: "mock-access-key-id",
        AccessKeySecret: "mock-access-key-secret",
        SecurityToken: "mock-security-token",
        Bucket: "mock-bucket",
        Path: "/mock/path",
      },
    }, { status: 200 });
  }),
];


将 upload.handlers.ts 导入到 handlers.ts

// src/mock/handlers.ts
import { uploadHandlers } from './handlers/upload.handlers';

export const handlers = [
  ...uploadHandlers,
  // 将其他模块的处理程序添加到此处
];

在node.ts中启动服务

// src/mocks/node.ts
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
 
export const server = setupServer(...handlers)

export const enableMocking = async () => {
  if (process.env.ENV !== "development_mock") {
    return;
  }
  return server.listen()
}

在入口文件注册mock服务

// main.ts
import { enableMocking } from './mocks/node'

async function bootstrap() {
  // ...
  await enableMocking();
  // ... 
  await app.listen(serverConfig['port'], '0.0.0.0');
}
bootstrap();

通过控制不同的key传入,可以达到不同的接口模拟效果;

6. 总结

Mock Service Worker (MSW) 是现代前端开发者手中的一把利器。它不仅可以在开发过程中为你模拟后端 API,从而摆脱对后端服务的依赖,还可以帮助你在测试中创建一致且可预测的测试环境。MSW 的灵活性和强大功能使得它在前端开发流程中的地位越来越重要。

通过使用 MSW,前端开发者可以更从容地面对开发和测试中的各种挑战,不再受制于后端服务的限制,最终提升了开发效率和代码质量。无论是初学者还是经验丰富的开发者,MSW 都是一款值得深入学习和掌握的工具。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352