Skip to content

搭建 mock 接口

基本介绍

为了方便前端开发人员快速搭建页面而不用等待后端真实接口。awe-axios 封装了一套 mock 接口的解决方案。你只需要做简单的配置就能将一个真实的接口变为mock接口,并在开发和生产环境下可以相互转换。

这里我们致敬 msw,它真实做到了从网络层面进行拦截

基本使用

想要将真实接口改为 mock 接口,只需要填写 mock 配置即可,mock配置项接受一个函数,如下所示:

ts
// 全局打开 mock 功能
MockAPI.on();
@HttpApi('http://localhost:3000/users')
class UserApi {
  @Get({
    // mock 配置,后接处理器函数
    mock: ctx => {
      return HttpResponse.json({
        data: [
          { id: 1, name: 'Alice' },
          { id: 2, name: 'Bob' },
        ],
      });
    },
  })
  getUsers(): any {}
}
let userApi = new UserApi();
// 注意:函数柯里化调用方式,而不是直接调用getUsers()
let { data } = await userApi.getUsers()();
console.log(data);

调用接口返回的data结果为:

json
{
  "data": [
    { "id": 1, "name": "Alice" },
    { "id": 2, "name": "Bob" }
  ]
}

在这里我们需要注意几个点:

  1. ⭐ ⭐在使用mock接口前你需要先打开全局mock功能,MockAPI.on()
  2. mock接口的调用方式是函数柯里化调用getUsers()(),而不是直接调用
  3. 处理器函数接受一个ctx对象,这个ctx对象就是处理器参数,其包含了请求参数和响应对象,你可以通过它来获取传入的参数和设置响应数据。

处理器函数

多个处理器函数

也许有许多人会认为函数柯里化调用很奇怪,事实上awe-axios采用函数柯里化是有很多好处的,它能让你分步传参

  1. 第一个参数传入的是必要的请求参数
  2. 第二个参数传入的是处理器类型

这就意味着在第二次调用时能提供多种mock处理方式。比如下面的案例,我们一个mock接口可以提供成功和失败两种状态的处理方式:

ts
@HttpApi({
  baseURL: 'http://localhost:3000',
  url: '/users',
})
class UserApi {
  @Get({
    mock: {
      handlers: {
        success: ctx => {
          return HttpResponse.json({
            data: [
              { id: 1, name: 'Alice' },
              { id: 2, name: 'Bob' },
            ],
          });
        },
        error: ctx => {
          return HttpResponse.error();
        },
      },
    },
  })
  getUsers(): any {}
}
let userApi = new UserApi();
let { data } = await userApi.getUsers()('success');
console.log(data);

当然这样写实在太丑陋了,嵌套非常深,awe-axios还提供了另一种写法就是直接在mockHandlers中单独配置处理器函数,这些处理器函数会最终与mock.handlers合并,如下所示:

ts
@HttpApi('http://localhost:3000/users/')
class UserApi {
  @Post({
    url: '/pages/:page/:size',
    mockHandlers: {
      success: async ({ request }) => {
        const data = await request.json();
        const { page, size } = data as { page: number; size: number };
        // 1,10
        console.log(page, size);
        return HttpResponse.json({
          data: [
            { id: 1, name: 'Alice' },
            { id: 2, name: 'Bob' },
          ],
        });
      },
      error: () => {
        return HttpResponse.error();
      },
      default: () => {
        return HttpResponse.json({
          data: [
            { id: 1, name: 'Alice' },
            { id: 2, name: 'Bob' },
          ],
        });
      },
    },
  })
  getUserPages(@BodyParam() qo: { page: number; size: number }): any {}
}
let userApi = new UserApi();
let { data } = await userApi.getUserPages({ page: 1, size: 10 })('success');
console.log(data);

同名处理器函数

如果你在mock.handlersmockHandlers中配置了同名的处理器函数,那么awe-axios会优先使用mock.handlers中的处理器函数。

默认处理器函数

刚才基本使用的案例中,我们只配置了一个处理器函数,这个处理器函数本质上就是一个名为defulat默认处理函数,也就是说你其实可以这么定义它:

ts
MockAPI.on();
@HttpApi('http://localhost:3000/users')
class UserApi {
  @Get({
    // mock 配置,后接处理器函数
    mock: {
      handlers: {
        default: ctx => {
          return HttpResponse.json({
            data: [
              { id: 1, name: 'Alice' },
              { id: 2, name: 'Bob' },
            ],
          });
        },
      },
    },
  })
  getUsers(): any {}
}

并通过如下方式进行调用:

ts
let userApi = new UserApi();
// 等价于getUsers()();
let { data } = await userApi.getUsers()('defualt');
console.log(data);

awe-axios中提供了默认的处理器函数,你的单个处理器函数只是覆盖了这个默认的处理器函数,如果你不配置处理器函数,那么它会调用默认的处理器函数。

ts
@HttpApi({
  baseURL: 'http://localhost:3000',
  url: '/users',
})
class UserApi {
  @Get({
    mock: {},
  })
  getUsers(): any {}
}
let userApi = new UserApi();
let { data } = await userApi.getUsers()();
console.log(data);

结果为:

json
{ "message": "welcome to use awe-axiosMock" }

处理器函数原理

你可能会很好奇,为什么配置一个或一组函数就能让mock接口生效?其实awe-axios底层采用了mswResponse resolver,这个Response resolver就是处理器函数保证函数。它需要你自己定义请求地址和处理器函数。比如:

ts
// 定义Response resolver
http.get('http://localhost:3000/users/groups', ({ request }) => {
  console.log(request.url);
  return HttpResponse.json({
    data: 'a',
  });
}),
const server = setupServer(...handlers);
server.listen();

可以看到原生的定义方式中,需要使用mswhttp.get来定义一个Response resolver,你需要传入请求地址和处理器函数。

awe-axios只是做了简化了工作,你不再需要设置mock接口的路径,awe-axios会自动读取@HttpApi@Get这类装饰器的配置,以及mock配置,并自动生成mswResponse resolver,然后注册。

HttpResponse

HttpResponsemsw提供的方便我们响应数据的API,具体你可以参见msw

构造函数

HttpResponse类具有与Fetch API Response类完全相同的构造函数签名。这包括静态响应方法,如Response.json()Response.error()

ts
class HttpResponse {
  constructor(
    body:
      | Blob
      | ArrayBuffer
      | TypedArray
      | DataView
      | FormData
      | ReadableStream
      | URLSearchParams
      | string
      | null
      | undefined,
    options?: {
      status?: number;
      statusText?: string;
      headers?: HeadersInit;
    },
  );
}

通过调用这个构造函数,你可以创建一个HttpResponse实例,并设置响应数据和响应头。

ts
// 这与 "new Response()" 同义。
new HttpResponse('Not found', {
  status: 404,
  headers: {
    'Content-Type': 'text/plain',
  },
});

常用静态方法

下面我会介绍一些常用的HttpResponse静态方法。

json

json方法可以将响应数据转换为json格式,并设置响应头Content-Type: application/json

ts
return HttpResponse.json(
  {
    errorMessage: 'Missing session',
  },
  { status: 401 },
);

error

error方法可以模拟一个network error

ts
return HttpResponse.error();

自定义方法

HttpResponse类还附带了一组自定义的静态方法,以简化响应声明。这些方法在Fetch API规范中没有替代品,完全是库特定的。

text

HttpResponse.text(body, init)
创建一个带有Content-Type: text/plain头和给定响应体的Response实例。

ts
HttpResponse.text('Hello world!');

html

HttpResponse.html(body, init)
创建一个带有Content-Type: text/html头和给定响应体的Response实例。

ts
HttpResponse.html(`<p class="greeting">Hello world!</p>`);

使用原生Response

您绝对可以在您的响应解析器中使用原生的 Fetch API Response 实例。MSW 是建立在标准的请求和响应基础之上的,因此您可以随时使用它们。只需要在处理器函数参数中获取Response对象即可。处理器函数参数即将介绍。

处理器函数参数

处理器函数能够接受一个参数,这个参数本质上就是一个上下文对象,包含了请求参数和响应对象,你可以通过它来获取传入的参数和设置响应数据。它有如下属性:

PropertyTypeDescription
requestRequestFetch API Request representation of the intercepted request.
requestIdstringUUID representing the intercepted request.
paramsRecord<string, string | string[]>Request path parameters (e.g. :userId).
cookiesRecord<string, string>Parsed request cookies.

通过它我们能够获取请求参数,并设置响应数据。下面列举了一些常用接受参数响应数据的场景:

接受查询参数

request是一个查询参数对象,你可以通过它获取查询参数。

js
@HttpApi('http://localhost:3000/users')
class UserApi {
  @Get({
    url: '/pages',
    mock: ({ request }) => {
      const url = new URL(request.url);
      const page = url.searchParams.get('page');
      const size = url.searchParams.get('size');
      // 1,10
      console.log(page, size);
      return HttpResponse.json({
        data: [
          { id: 1, name: 'Alice' },
          { id: 2, name: 'Bob' },
        ],
      });
    },
  })
  getUserPages(@QueryParam('page') page: number, @QueryParam('size') size: number): any {}
}
let userApi = new UserApi();
let { data } = await userApi.getUserPages(1, 10)();
console.log(data);

接受路径参数

通过params属性,你可以获取路径参数。

js
@HttpApi('http://localhost:3000/users/')
class UserApi {
  @Get({
    url: '/pages/:page/:size',
    mock: ({ params }) => {
      const { page, size } = params;
      // 1,10
      console.log(page, size);
      return HttpResponse.json({
        data: [
          { id: 1, name: 'Alice' },
          { id: 2, name: 'Bob' },
        ],
      });
    },
  })
  getUserPages(@PathParam('page') page: number, @PathParam('size') size: number): any {}

接受请求体参数

通过request属性,你可以获取请求体参数。

js
test.only('接受请求体参数', async () => {
  @HttpApi('http://localhost:3000/users/')
  class UserApi {
    @Post({
      url: '/pages/:page/:size',
      mock: async ({ request }) => {
        const data = await request.json();
        const { page, size } = data as { page: number; size: number };
        // 1,10
        console.log(page, size);
        return HttpResponse.json({
          data: [
            { id: 1, name: 'Alice' },
            { id: 2, name: 'Bob' },
          ],
        });
      },
    })
    getUserPages(@BodyParam() qo: { page: number; size: number }): any {}
  }
  let userApi = new UserApi();
  let { data } = await userApi.getUserPages({ page: 1, size: 10 })();
  console.log(data);

注意

你需要改为post请求方式才能传递请求体参数。