使用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 都是一款值得深入学习和掌握的工具。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。