Back to blog
May 30, 2024
3 min read

Mock数据方案及其实现

Mock数据方案及其实现

Mock数据 msw

在看这个结构之前,我们要先看看默认的 mock 接口的内容都有哪些

MSW是什么?Mock Service Worker(MSW)是一个旨在浏览器和Node.js中使用的API模拟库。它能够拦截和模拟网络请求,这对开发者来说极为有用,因为它允许你在不需要实际后端服务器的情况下模拟网络响应,从而实现无缝的测试体验​,在浏览器中的用法如下。

其实在应用的根目录public目录下有一个 mockServiceWorker.js ,大伙有没有疑惑这东西是干什么的?

一般来说public中存放的是公共资源,如HTML文件、图标或可公开访问的静态资源。这mockServiceWorker.js是什么? 这个文件主要是为了解决模拟数据问题而存在的,这个类库本身叫做MSW。然儿在浏览器端使用 Mock Service Worker (MSW) 需要添加 mockServiceWorker.js 文件,这主要是因为 MSW 利用了 Service Worker 技术来拦截和处理网络请求。 Service Worker 是一种在浏览器后台运行的脚本,可以拦截和修改访问和管理的网络请求。MSW 使用 Service Worker 来拦截发出的请求,并根据定义的模拟逻辑返回响应。罗列一下里面的关系如下:

  1. 我们的需求是在本地开发前端应用时使用相对真实的模拟服务
  2. 使用的是msw类库去实现
  3. 然儿在浏览器端使用msw需要添加mockServiceWorker.js
  4. 这个文件是一个预先编写的 Service Worker 脚本,其职责是在网络请求级别拦截请求。
  5. 所以当前文件是实现模拟服务的必要静态文件

我们来看看整体的mock结构,让后在细化里面的内容

├── _mock
│   ├── assets.js
│   ├── handlers
│   │   ├── _org.js
│   │   └── _user.js
│   ├── index.js
│   └── utils.js
import { setupWorker } from 'msw/browser';

import orgMockApi from './handlers/_org';
import userMockApi from './handlers/_user';

const handlers = [...userMockApi, ...orgMockApi];
// 创建一个模拟服务器实例worker,并导出
export const worker = setupWorker(...handlers);

setupWorker:从 msw/browser 导入的函数,用于在浏览器中设置服务工作者(Service Worker)。 handlers:从两个模块 ./handlers/_org 和 ./handlers/_user 导入的请求处理程序列表。这些处理程序用于拦截并模拟相应的 API 请求。 worker:创建并导出了一个服务工作者实例,该实例使用所有定义的请求处理程序来拦截和模拟 API 请求。

// 启动模拟服务器
worker.start();

工作原理:当在项目中调用 worker.start() 时,会发生如下几个步骤:

  1. MSW 通过 mockServiceWorker.js 脚本在浏览器中注册一个 Service Worker。这个过程涉及到将 mockServiceWorker.js 文件作为 Service Worker 安装到浏览器中。
  2. 一旦注册成功,这个 Service Worker 就开始拦截页面发出的所有或特定的 HTTP 请求(取决于如何配置 MSW)。
  3. 对于拦截的每个请求,Service Worker 会根据定义的处理逻辑生成响应。这包括根据模拟的定义返回数据、错误或其他网络响应。
  4. 由于所有的拦截和数据模拟都是在 Service Worker 中完成的,它不会侵入或修改应用的实际代码。这使得模拟过程对应用透明,且易于启用或禁用。

使用模拟的API

一旦服务工作器启动,所有定义的请求处理程序将自动拦截对应的网络请求,并返回模拟的响应。 例如,当你的应用尝试访问 /api/user,MSW 将拦截这个请求并返回你在处理程序中定义的模拟响应。

tip: 那什么是 Service Worker?是一种运行在浏览器背后的脚本,允许开发人员以编程方式控制缓存和网络请求的技术,使网站能够提供更丰富的离线体验和性能优化。

orgMockApi

import { http, HttpResponse } from 'msw';

import { ORG_LIST } from '@/_mock/assets';
import { OrgApi } from '@/api/services/orgService';

const orgList = http.get(`/api${OrgApi.Org}`, () => {
  return HttpResponse.json({
    status: 0,
    message: '',
    data: ORG_LIST,
  });
});

export default [orgList];

http 和 HttpResponse:从 msw 导入,用于创建请求处理程序和生成 HTTP 响应。 ORG_LIST:导入的组织列表数据,用于模拟 API 响应数据(是一组默认的写死的数据内容) OrgApi:包含组织相关 API 路径的枚举或对象。

export enum OrgApi {
  Org = '/org',
}

orgList http.get:定义了一个拦截 GET 请求的处理程序,URL 为 /api/org。 回调函数:模拟服务器的响应,返回 ORG_LIST 数据,状态码为 0,表示成功。

userMockApi

// 生成大量虚假(但看上去真实)数据,用于测试和开发。
import { faker } from '@faker-js/faker';
// 从 msw 导入,用于创建请求处理程序、模拟延迟和生成 HTTP 响应。
import { delay, http, HttpResponse } from 'msw';
// 包含用户相关 API 路径的枚举或对象。
import { UserApi } from '@/api/services/userService';
// 导入的用户列表数据,用于模拟 API 响应数据(包含admin和test的两个默认测试用户)
import { USER_LIST } from '../assets';

// 登录功能:定义了一个拦截 POST 请求的处理程序,URL 为 /api/signin。
const signIn = http.post(`/api${UserApi.SignIn}`, async ({ request }) => {
  // 使用 await request.json() 解析请求体。
  const { username, password } = await request.json();
  // 在 USER_LIST 中查找匹配的用户并验证密码。
  const user = USER_LIST.find((item) => item.username === username);

  if (!user || user.password !== password) {
    return HttpResponse.json({
      status: 10001,
      message: 'Incorrect username or password.', // 用户名或密码不正确
    });
  }
  // 成功时生成 accessToken 和 refreshToken。
  return HttpResponse.json({
    status: 0,
    message: '',
    data: {
      user,
      accessToken: faker.string.uuid(),
      refreshToken: faker.string.uuid(),
    },
  });
});

// 获取用户列表:定义了一个拦截 GET 请求的处理程序,URL 为 /api/user。
const userList = http.get('/api/user', async () => {
  await delay(1000);
  // 模拟获取用户列表的请求,延迟 1 秒后返回生成的假用户数据。
  return HttpResponse.json(
    Array.from({ length: 10 }).map(() => ({
      fullname: faker.person.fullName(),
      email: faker.internet.email(),
      avatar: faker.image.avatar(),
      address: faker.location.streetAddress(),
    })),
    {
      status: 200,
    },
  );
});

export default [signIn, userList];
export enum UserApi {
  SignIn = '/auth/signin',
  SignUp = '/auth/signup',
  Logout = '/auth/logout',
  Refresh = '/auth/refresh',
  User = '/user',
}