使用 Mirage JS 和 Vue.js 設置 API 模擬

已發表: 2022-03-10
快速總結 ↬本文介紹 Mirage JS,這是一個 API 模擬庫,可讓您構建、測試和共享完整的工作 JavaScript 應用程序,而無需依賴任何後端 API 或服務。 您還將學習如何使用漸進式前端框架 Vue.js 設置 Mirage JS。

在 SPA 和 JAMstack 時代,API 和前端開發之間一直存在關注點分離。 幾乎所有可以在野外發現的 JavaScript 項目都與 Web 服務或 API 交互,並將其用於身份驗證或獲取與用戶相關的數據。

因此,每當您在處理一個項目並且後端團隊仍未實現必要的 API 或您需要快速測試某個功能時,您可以選擇以下一些選項:

  • 您可以代理到實際後端的本地運行版本,在大多數情況下,作為前端開發人員,您不會擁有該版本。
  • 您可以註釋掉實際請求並替換為模擬數據。 (這沒關係,但不是很好,因為您需要撤消它才能投入生產,並且您可能無法處理網絡狀態和延遲。)

什麼是 API 模擬?

API 模擬是對實際 API 的模仿或模擬。 它主要是為了攔截應該向實際後端 API 發出的請求,但這種模擬存在於您的前端。

為什麼 API 模擬很重要

API 模擬在很多方面都非常重要:

  1. 在構建功能之前不依賴於生產 API,從而獲得非常好的前端開發體驗。
  2. 您可以共享您的整個前端,它可以在不依賴實際後端 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 servenpm 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導入ServerModel

  • 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 快速入門