使用 Mirage JS 和 Vue.js 设置 API 模拟
已发表: 2022-03-10在 SPA 和 JAMstack 时代,API 和前端开发之间一直存在关注点分离。 几乎所有可以在野外发现的 JavaScript 项目都与 Web 服务或 API 交互,并将其用于身份验证或获取与用户相关的数据。
因此,每当您在处理一个项目并且后端团队仍未实现必要的 API 或您需要快速测试某个功能时,您可以选择以下一些选项:
- 您可以代理到实际后端的本地运行版本,在大多数情况下,作为前端开发人员,您不会拥有该版本。
- 您可以注释掉实际请求并替换为模拟数据。 (这没关系,但不是很好,因为您需要撤消它才能投入生产,并且您可能无法处理网络状态和延迟。)
什么是 API 模拟?
API 模拟是对实际 API 的模仿或模拟。 它主要是为了拦截应该向实际后端 API 发出的请求,但这种模拟存在于您的前端。
为什么 API 模拟很重要
API 模拟在很多方面都非常重要:
- 在构建功能之前不依赖于生产 API,从而获得非常好的前端开发体验。
- 您可以共享您的整个前端,它可以在不依赖实际后端 API 的情况下工作。
什么是幻影 JS?
Mirage JS 是 5 年前创建的,在 Sam Selikoff 于 2020 年 1 月 24 日在 Twitter 上正式宣布发布之前,它在 Ember 社区中被广泛使用。
Mirage JS 解决了测试后端 API 的痛点,无需依赖这些 API。 它通过模拟生产 API 来提供无缝的前端开发体验。
Mirage JS 是一个用于 Vue.js、React、Angular 和 Ember 框架的 API 模拟库
是什么让 Mirage JS 成为更好的选择?
API 模拟还有其他选项(例如 Axios 拦截器、Typicode 的 JSON 服务器等),但我认为 Mirage 非常有趣的是它不会妨碍您的开发过程(如您所见当我们稍微用 Vue 设置它时)。 它重量轻但功能强大。
它附带开箱即用的电池,可让您复制真实的生产 API 消耗场景,例如使用计时选项模拟慢速网络。
Mirage JS 和 Vue.js 入门
既然您知道 Mirage JS 是什么以及为什么它对您的前端开发工作流程很重要,那么让我们看看如何使用渐进式 Web 框架 Vue.js 来设置它。
创建一个绿地(干净安装)Vue 项目
使用 Vue CLI,通过进入您希望在其中创建和运行项目的目录(在您的终端中)创建一个新的Vue.js项目:
vue create miragejs-demo-vue
上面的命令将建立一个新的 Vue 项目,您现在可以cd
进入并运行yarn serve
或npm run serve
。
#安装幻影JS
现在让我们通过运行以下命令将 Mirage JS 作为开发依赖项安装到Vue.js项目中:
yarn add -D miragejs
或者,如果您使用的是 NPM,请运行以下命令:
npm install --save-dev miragejs
就是这样! Mirage JS 现在已安装在我们的Vue.js项目中。
让我们嘲笑一些东西
安装 Mirage JS 后,让我们看看我们如何配置它以与 Vue 对话并模拟一个基本的 todos(一个返回 todos 列表的 API)API。
定义您的服务器
首先,我们需要在Vue.js项目的/src
目录中创建一个server.js文件。 之后,添加以下内容:
import { Server, Model } from 'miragejs' export function makeServer({ environment = "development" } = {}) { let server = new Server({ environment, models: { todo: Model, }, seeds(server) { server.create("todo", { content: "Learn Mirage JS" }) server.create("todo", { content: "Integrate With Vue.js" }) }, routes() { this.namespace = "api" this.get("/todos", schema => { return schema.todos.all() }) }, }) return server }
代码解释
首先, server.js文件是您设置 Mirage JS 以创建其模拟(假)服务器的新实例的方式,该实例将拦截您在应用程序中进行的与您定义的路由匹配的所有 API 调用。
现在,我同意上述内容一开始可能会让人不知所措,但让我们仔细看看这里发生了什么:
import { Server, Model } from 'miragejs'
从上面的代码片段中,我们从miragejs
导入Server
和Model
。
-
Server
这是 Mirage 公开的一个类,用于帮助我们实例化 Mirage JS 服务器的新实例,以“充当”我们的假服务器。 -
Model
Mirage 公开的另一个类,用于帮助创建由 Mirage 的 ORM 提供支持的模型(模型确定 Mirage JS 数据库条目的结构)。
export function makeServer({ environment = "development" } = {}) {}
以上基本上是从src/server.js
导出一个名为makeServer
的函数。 您还可以注意到我们正在传递一个环境参数并将 Mirage 的环境模式设置为development
(您将在本文后面看到我们通过测试环境)。
makeServer
的主体
现在我们在makeServer
主体中做一些事情。 让我们来看看:
let server = new Server({})
我们正在实例化 Server 类的一个新实例,并向它传递一个配置选项。 配置选项的内容有助于设置 mirage:
{ environment, models: { todo: Model, }, seeds(server) { server.create("todo", { content: "Learn Mirage JS" }) server.create("todo", { content: "Integrate With Vue.js" }) }, routes() { this.namespace = "api" this.get("/todos", schema => { return schema.todos.all() }) }, }
首先,我们传递我们在函数定义中初始化的environment
参数。
models: { todo: Model, },
下一个选项是models
选项,它采用我们希望 Mirage 模拟的不同模型的对象。
在上面,我们只需要一个从 Model 类实例化的 todo 模型。
seeds(server) { server.create("todo", { content: "Learn Mirage JS" }) server.create("todo", { content: "Integrate With Vue.js" }) },
下一个选项是种子方法,它接受一个名为server
的参数。 种子方法有助于为我们的模型创建种子(种子是初始数据或 Mirage 数据库的条目)。 在我们的例子中,我们为 todo 模型创建种子:
server.create("todo", { content: "Learn Mirage JS" }) server.create("todo", { content: "Integrate With Vue.js" })
所以服务器有一个 create 方法,它的第一个参数是一个与模型名称相对应的字符串,然后是一个包含特定种子的属性或属性的对象。
routes() { this.namespace = "api" this.get("/todos", schema => { return schema.todos.all() }) },
最后,我们有定义各种路由的 routes 方法(路由是我们的模拟 API 端点) Mirage JS 将模拟。 让我们看一下方法的主体:
this.namespace = "api"
这一行为所有路由设置了命名空间,这意味着我们的 todo 路由现在可以从 /api/todos 访问。
this.get("/todos", schema => { return schema.todos.all() })
上面创建了一个 get 路由,它是使用this.get()
方法的处理程序。 get()
方法需要路由,即“/todos”和一个以schema
作为参数的处理函数。 模式对象是您与 Mirage 的 ORM 交互的方式,该 ORM 由 Mirage JS 内存数据库提供支持。
最后:
return schema.todos.all()
我们使用 Mirage 的 ORM 提供的模式对象返回所有待办事项的列表。
src/main.js
所以我们已经完成了src/server.js
的设置,但是 Vue 并不知道(至少现在还不知道)。 所以让我们将它导入到我们的main.js文件中,如下所示:
import { makeServer } from "./server"
然后我们像这样调用makeServer
函数:
if (process.env.NODE_ENV === "development") { makeServer() }
上述if
条件是确保 mirage 仅在开发中运行的守卫。
设置完成!
现在我们已经使用 Vue 设置了 Miragejs。 让我们看看它的实际效果。 在我们的App.vue文件中,我们将清除内容并替换为以下代码段:
<template> <ul> <li v-for="todo in todos" v-bind:key="todo.id">{{ todo.content }}</li> </ul> </template> <script> export default { name: 'app', data() { return { todos: [] } }, created() { fetch("/api/todos") .then(res => res.json()) .then(json => { this.todos = json.todos }) } } </script>
如果你对 Vue.js 很熟悉,上面的内容也不是什么新鲜事,但为了完整起见,我们所做的是在创建App.vue
组件时使用fetch
发出 API 请求,然后传入返回的数据到我们组件状态的 todos 数组。 之后,我们使用 v-for 来迭代 todos 数组并显示每个 todo 的 content 属性。
Mirage JS 部分在哪里?
如果您注意到,在我们的 App.vue 组件中,我们没有做任何特定于 Mirage 的事情,我们只是像往常一样进行 API 调用。 Mirage 的这个特性对于 DX 来说真的很棒,因为在后台,Mirage 会在您开发时拦截与 src/server.js 中定义的任何路由匹配的任何请求。
这非常方便,因为当您在生产环境中时,只要路由与您的生产 API 端点匹配,您就不需要做任何工作来切换到实际的生产服务器。
所以通过yarn serve
重启你的 Vue 开发服务器来测试 Mirage JS。
您应该会看到一个包含两个待办事项的列表。 您会发现非常有趣的一件事是,我们不需要运行终端命令来启动 Mirage,因为它通过作为 Vue.js 应用程序的一部分运行来消除这种开销。
Mirage JS 和 Vue 测试工具
如果您已经在 Vue 应用程序中使用 Vue Test-utils,那么您会发现 Mirage 可以轻松地使用它来模拟网络请求,这让您感到非常兴奋。 让我们看一个使用我们的 todos 应用程序设置的示例。
我们将使用 Jest 进行单元测试。 因此,如果您一直在关注,您几乎可以使用 Vue CLI 来安装@vue/unit-jest
插件,如下所示:
vue add @vue/unit-jest
上面将安装@vue/cli-plugin-unit-jest
和@vue/test-utils
开发依赖项,同时还会创建一个tests
目录和一个jest.config.js文件。 它还将在我们的package.json scripts
部分添加以下命令(非常简洁):
"test:unit": "vue-cli-service test:unit"
让我们测试!
我们将更新我们的App.vue看起来像这样:
<!-- src/App.vue --> <template> <div v-if="serverError" data-test> {{ serverError }} </div> <div v-else-if="todos.length === 0" data-test> No todos! </div> <div v-else> <ul> <li v-for="todo in todos" v-bind:key="todo.id" :data-test > {{ todo.content }} </li> </ul> </div> </template> <script> export default { name: "app", data() { return { todos: [], serverError: null, } }, created() { fetch("/api/todos") .then(res => res.json()) .then(json => { if (json.error) { this.serverError = json.error } else { this.todos = json.todos } }) }, } </script>
上面的片段中没有发生真正的史诗; 我们只是在构建允许我们将通过单元测试实现的网络测试。
尽管 Vue CLI 已经为我们添加了一个/tests
文件夹,但我发现当我的测试放置在他们正在测试的组件附近时,它的体验要好得多。 因此,在src/
中创建一个/__tests__
文件夹,并在其中创建一个App.spec.js文件。 (这也是 Jest 推荐的方法。)
// src/__tests__/App.spec.js import { mount } from "@vue/test-utils" import { makeServer } from "../server" import App from "../App.vue" let server beforeEach(() => { server = makeServer({ environment: "test" }) }) afterEach(() => { server.shutdown() })
因此,为了设置我们的单元测试,我们从@vue/test-utils
导入mount
方法,导入我们之前创建的 Miragejs 服务器,最后导入App.vue
组件。
接下来,我们使用beforeEach
生命周期函数来启动 Mirage JS 服务器,同时在测试环境中传递。 (请记住,我们默认将环境设置为development
。)
最后,我们在afterEach
生命周期方法中使用server.shutdown
关闭服务器。
我们的测试
现在让我们充实我们的测试(我们将采用 Mirage js 文档的快速入门部分。所以您的App.spec.js最终看起来像这样:
// src/__tests__/App.spec.js import { mount } from "@vue/test-utils" import { makeServer } from "./server" import App from "./App.vue" let server beforeEach(() => { server = makeServer({ environment: "test" }) }) it("shows the todos from our server", async () => { server.create("todo", { id: 1, content: "Learn Mirage JS" }) server.create("todo", { id: 2, content: "Integrate with Vue.js" }) const wrapper = mount(App) // let's wait for our vue component to finish loading data // we know it's done when the data-testid enters the dom. await waitFor(wrapper, '[data-test]') await waitFor(wrapper, '[data-test]') expect(wrapper.find('[data-test]').text()).toBe("Learn Mirage JS") expect(wrapper.find('[data-test]').text()).toBe("Integrate with Vue.js") }) it("shows a message if there are no todo", async () => { // Don't create any todos const wrapper = mount(App) await waitFor(wrapper, '[data-test]') expect(wrapper.find('[data-test]').text()).toBe("No todos!") }) // This helper method returns a promise that resolves // once the selector enters the wrapper's dom. const waitFor = function(wrapper, selector) { return new Promise(resolve => { const timer = setInterval(() => { const todoEl = wrapper.findAll(selector) if (todoEl.length > 0) { clearInterval(timer) resolve() } }, 100) }) } afterEach(() => { server.shutdown() })
注意:我们在这里使用了一个助手(如 Mirage JS 文档中定义的那样)。 它返回一个承诺,让我们知道我们正在测试的元素何时已经在 DOM 中。
现在运行yarn test:unit
。
此时您的所有测试都应该通过。
使用 Mirage JS 测试不同的服务器状态
我们可以改变我们的 Mirage JS 服务器来测试不同的服务器状态。 让我们看看如何。
// src/__tests__/App.spec.js import { Response } from "miragejs"
首先,我们从 Mirage 导入Response
类,然后我们创建一个新的测试场景,如下所示:
it("handles error responses from the server", async () => { // Override Mirage's route handler for /todos, just for this test server.get("/todos", () => { return new Response( 500, {}, { error: "The database is taking a break.", } ) }) const wrapper = mount(App) await waitFor(wrapper, '[data-test]') expect(wrapper.find('[data-test]').text()).toBe( "The database is taking a break." ) })
运行你的测试,一切都应该通过。
结论
本文旨在向您介绍 Mirage JS,并向您展示它如何提供更好的前端开发体验。 我们看到了 Mirage JS 为解决的问题(在没有任何实际后端 API 的情况下构建生产就绪的前端)以及如何使用 Vue.js 进行设置。
尽管本文只介绍了 Mirage JS 的功能,但我相信它足以让您入门。
- 您可以浏览文档以及加入 Mirage JS discord 服务器。
- 本文的支持 repo 可在 GitHub 上找到。
参考
- 海市蜃楼文档
- Mirage Vue 快速入门