API 開發、測試、除錯一次到位!使用 MSW 快速上手 Mock API

什麼是 API?

在認識 API 以前,我們直接從生活中的例子來認識介面(Interface)。

忙碌的午後,「小美」想要喝罐可爾必思。於是她前往公司樓下的飲料販賣機,在「操作面板上」投硬幣跟按按鈕,販賣機裡的「系統」判斷金額是否符合,以及是按下哪個飲料的對應按鈕。判斷結束後,就把一罐可爾必思推出來。最後小美從取物口拿出,大口地暢飲。

在上述例子裡,飲料販賣機的控制面板即為人機互動的「介面」,也就是等下待會要講的「API」。而小美呢,則是介面的使用者,對應到軟體開發的領域上是指 API 的使用者,也就是我們這些應用程式的開發者。

API(Application Programming Interface)的字面意思是「應用程式介面」。就像飲料販賣機的介面一樣,API 是應用程式之間一套明確定義的溝通方法和接口。使用 API 的過程中,不需要理解內部複雜的運作,只需要知道要呼叫什麼方法,傳入對應參數,就能取得想要的結果。

API是什麼?認識 Web API、HTTP 和 JSON 資料交換格式

Web 應用程式中的 API

在 Web 應用程式的開發情境下的 API 被稱為 Web API,在 Web API 作用時,客戶端(前端)和伺服器端(後端)會透過 HTTP 通訊協定來進行請求與回應。

原圖取自:ALPHA Camp Blog

作為前端的開發者,為了要取得存放在系統的資訊,常常會需要以 Web API 來發送請求與後端溝通。因此通常需要做的事情有以下:

  • 閱讀 API 文件
  • 如果是第三方開發的應用(例如 Google、Facebook),可能需要註冊、驗證等程序
  • 撰寫有關 Web API 串接的函式
  • 在適當時機點呼叫對應的串接函式

而後端的開發者,則是將擁有的資料庫開放給第三方使用。因此開發步驟大致會是這樣:

  • 撰寫一個明確的 API 文件,清楚定義 API 的使用方式及格式等等;
  • 按照文件開發一個 Web API,提供可外部呼叫的方法,用這個方式做為使用介面。

控制 API 的回傳結果,可能嗎?

前端開發時,十之八九都會需要實作 API 的串接。你可能會先建立相關的函式模組,然後在元件或是狀態管理的程式碼中匯入,在適當時機點發出 API 的請求。

問題來了,如果想要測試 API 回傳不同的結果下,元件的操作邏輯是否正常;或是現在正處於雛型(Prototype)開發的階段,在後端的 Web API 還沒完成的情況下,前端要超前部署產出具體的半成品等,前端這邊是不是可以先實作好 API 串接的部分,而且還能隨意控制 API 回傳的結果呢?

舉個簡單的例子,在 api.js 中,定義了所有跟 API 串接的函式模組,其中,isLoggedIn 函式是檢查使用者是否已登入。

  • api.js

<p>CODE: https://gist.github.com/ACbosong/40d38a560b076efda88b48db602fa65b.js</p>

在元件- App.js 中,我們匯入isLoggedIn 函式,並且在適當時機點呼叫它,如果已經登入,執行 A,否則執行 B。

  • App.js

<p>CODE: https://gist.github.com/ACbosong/3c597b5d8f2b979f1c9773ab18df41e7.js</p>

假設不變動 App.js 中的-const isLoggedIn = await isLoggedIn();

我們有辦法由前端控制 isLoggedIn 變數是 true 還是 false,來檢查相關的條件判斷嗎?

這個問題,當然是 YES 囉!這就是今天要講的-Mock API

API 開發、測試、除錯一次到位-Mock API

先來說文解字一下,「Mock」是從單元測試的概念延伸而來的,主要是模擬真實的物件或類別,但是省略內部複雜的行為,讓測試過程可以更專注在本身的測試重點上。

原圖取自:程式札記

 同樣地,比起透過真實的後端 server 取得資料,我們有時候會比較想要自己決定資料的樣子,甚至是定義 API 的入口點(entrypoint)和參數欄位等等,但同時又不想變動前端呼叫 API 串接的部分。可以完成以上種種行為的方式,就是 Mock API。

使用 Mock API 的理由

最普遍的使用需求,應該就是希望在初期的開發階段,前端不需要等後端實作完成後才能接著進行。除了讓開發時程不用花到雙倍的時間,前後端雙方也能透過 Mock API 的過程更具體討論實作細節,達成品質與速度都能雙贏的目的

除此之外,以下的場合也能應用 Mock API 來完成:

  • 撰寫測試案例:進行元件測試或 E2E 測試時,可以撰寫各種 HTTP 狀態,或是不同回應資料時的測試案例。
  • 開發時的除錯:藉由修改 mocking 的內容,幫助元件或頁面進行除錯。

向 MSW Say Hi!

原圖取自:Mock Service Worker

MSW(Mock Service Worker)是一套可以實現 Mock API 的工具。最大的特色是能在網路層(Network)發出實際的請求(Request),並透過 Service Worker 攔截,回傳已經定義好的資料內容。

另外,MSW 也擁有許多優點,像是:

  • 跟許多框架、工具都有很好的整合性,像是 React、Jest、Cypress 等。
  • 針對主流的 API 設計-RESTful 和 GraphQL 都有良好的支援。
  • 如果專案是以 TypeScript 開發,不須特別設定就能直接使用 TypeScript,享受強型別帶來的便利與好處。
  • 清楚好閱讀的官方文件,並且有範例程式,讓開發者能好上手。

因此跟其他相似的工具來比較,像是 MirageJS、json-server,MSW 的下載使用量平均超過它們的兩倍,並且有成長的趨勢。這也表示相關的社群資源和討論聲量也會愈來愈豐富。

原圖取自:npm trends

在熟悉 MSW 之前,我們先基礎認識它的底層機制-Service Worker,以及知道 RESTful 和 GraphQL 的差別。

Service Worker

Service Worker 是 Web Worker 的一種。除了可以在背景執行緒中運作,不影響使用者的網頁瀏覽體驗以外,最大特色是透過 cache 的機制來打造離線體驗。我們多少會碰到一些網站提供推播通知、暫存資料或是背景中同步等功能,這要多虧有 Service Worker 的實現。

原圖取自:netlify

導入 Service Worker 有幾個重點步驟:

  1. 檢查瀏覽器的支援度:基本上除了IE,大部分瀏覽器在某版本之後都有陸續支援,如果需要,可以加上檢查。

<p>CODE:https://gist.github.com/ACbosong/167bf86311a0d1bd1a23e9fa8fd5de32.js</p>

  1. 註冊:呼叫全域方法 navigator.serviceWorker.register() 進行註冊。由於這是個 promise 函式,可以透過 then catch 來做完成後的處理。

<p>CODE:https://gist.github.com/ACbosong/6901b5164754dfd86f7ef99f128e9a66.js</p>

  1. 安裝:註冊完後,會觸發 serviceWorker.js 中的 install 事件,執行瀏覽器的離線快取功能。

<p>CODE:https://gist.github.com/ACbosong/906e4551cd20d4122d615631a3146e53.js</p>

  1. 啟動:註冊完後,會觸發 serviceWorker.js 中的 active 事件,正式啟動 Service Worker。

<p>CODE:https://gist.github.com/ACbosong/0089aa6f74874ce50d9fcc7037d7bd3e.js</p>

  1. 存取請求內容:當在這個 Service Worker 的可控範圍內偵測到 HTTP 請求,會觸發 serviceWorker.js 中的 fetch 事件實作 cache 的機制。

<p>CODE:https://gist.github.com/ACbosong/085793970cfe227f7d393e884e582e07.js</p>

  1. 收發訊息:觸發 serviceWorker.js 中的 message 事件,處理各種請求階段的接收與發送訊息。

<p>CODE:https://gist.github.com/ACbosong/1f6ac4ccfae7c997c1666d23f3c2f348.js</p>

RESTful vs. GraphQL

RESTful 是一種 API 的設計風格,將有語意性的詞彙加上資料參數形成特定路由(Route),並運用 HTTP 各種定義的請求方式,向後端取得資源或是修改資源。

因此 RESTful 最大的特點是,可以透過路由的結構清楚知道資料欄位跟操作方式,適合用來設計簡單或是結構較為單純的 API。

原圖取自:how to graphql

GraphQL 是由 Facebook 在 2015 年發布的 open source。透過前端定義的資料結構,告訴後端返回相同資料結構的對應資料,來避免後端設計大量的 API route ,或是返回多餘的資料欄位。

不過這種靈活性也增加了複雜性,通常會比較適合用來設計具規模或是結構比較交錯複雜的 API。

原圖取自:how to graphql

MSW 初體驗

如果只是想先體驗看看,不用大費周章建立環境,可以參考 MSW 建立的範例程式,裡面有許多不同的範本專案。切到不同的專案環境後,就可以執行相關指令玩玩看囉

  1. clone 範本專案。執行以下指令,或是使用 GitHub Desktop 的 Clone Repository > URL 輸入以下的網址部分。

<p>CODE:https://gist.github.com/ACbosong/42a72cb4fb8c729854fbd58e37bdc85b.js</p>

  1. 切換到 examples 目錄。

<p>CODE:https://gist.github.com/ACbosong/06a210fa0ac41e345d7907ef0f128dd4.js</p>

  1. 安裝所有依賴套件。

<p>CODE:https://gist.github.com/ACbosong/3e9cbdc0f00041590a43058182f46cb4.js</p>

  1. 切換到特定目錄,例如:想看使用 React,加上 RESTful 的 API 設計會是什麼樣子,那麼就切換到 rest-react

<p>CODE:https://gist.github.com/ACbosong/23d5e9735e870240433ec25cb65f7c51.js</p>

  1. 執行以下指令,在本機建立 live server。

<p>CODE:https://gist.github.com/ACbosong/4a30aadf829788e877a97ec9bd278a00.js</p>

  1. 打開瀏覽器輸入 localhost:3001。正常的話,可以看到頁面上有個簡易表單。按 F12 觀察 console,就會出現 [MSW] Mocking enabled.的訊息,表示 MSW 已經在運作囉!
  1. 可以把 Dev Tool 切到 Network 觀察。在表單欄位隨意輸入後送出,會發現 Network 中發出了一個 login 的請求。
  1. 再切回 Console,可以看到 MSW 有印出請求的 Request、Reponse 的詳細內容。找到程式碼來對照看看,的確 MSW 發揮了作用,以程式中定義好的資料作為 Response 的資料,magic~

建置與導入 MSW

如果想引入專案環境中,其實非常簡單,基本上安裝 msw 這個套件就可以了。

<p>CODE:https://gist.github.com/ACbosong/6dd207c895d2ad3bddbd86704674acab.js</p>

不過不少人,包括我自己是用 webpack 來打包前端程式和建立 live server。因此接下來的建置流程會再多介紹 webpack 的導入方式。

  1. 安裝 MSW 以及執行 webpack 時所需的 plugins,作為開發用的依賴套件。

<p>CODE:https://gist.github.com/ACbosong/dd70af664d483231aae97a7f9c45b294.js</p>

  1. 在專案的根目錄下建立 public 的子目錄,如果已經有的話省略。
  2. src 的目錄下建立 mocks 的子目錄。
  3. mocks 的目錄下建立兩個檔案- handlers.ts(js) 以及 browser.ts(js),待會會提到更多這兩個檔案的實作部分。
  4. 在專案的根目錄下開啟終端機,執行以下指令來建立負責產生 Service Worker 的程式檔 mockServiceWorker.js。如果有興趣的話,可以從這份檔案來看 MSW 如何設計 Service Worker 的架構與各種生命週期的事件喔!

<p>CODE:https://gist.github.com/ACbosong/20e330a5f99803fbd9cd0082ec5fe97d.js</p>

  1. webpack.config.js 或特定的 webpack 設定檔中,在 plugins 部分新增跟 Service Worker 相關的設定。
  • CopyWebpackPlugin- 將 mockServiceWorker.js 複製到 live server 中。
  • WorkboxPlugin- 建立 Service Worker。

<p>CODE:https://gist.github.com/ACbosong/5055e46c155212da3399277be0576ce9.js</p>

  1. index.html 偵測當前瀏覽器有無支援 Service Worker,有的話就以 mockServiceWorker.js 來註冊 Service Worker。

<p>CODE:https://gist.github.com/ACbosong/b0a94a01e548b1616a3c16f141f158bf.js</p>

如果按照以上步驟做完的話,目錄結構應該會長這樣-

<p>CODE:https://gist.github.com/ACbosong/da4cd6a9d04bc1b542a9e5698533e4e9.js</p>

快速上手 MSW

handlers.ts(js) 是負責撰寫 mock API 的實作細節。通常會建立一個陣列變數-handlers 來存放各個 API 串接的實作。

<p>CODE:https://gist.github.com/ACbosong/2f4d7719c844bfb7365bfee3822b7b82.js</p>

舉上面範例程式的 login 請求作為例子,分別看看 RESTful 跟 GraphQL 這兩種 API 設計模式,MSW 會如何實作。

RESTful

從 msw 匯入 restrest 提供了各種模擬 HTTP 的請求方式實作-

  • rest.get():對應 GET 請求,取得資源。
  • rest.post():對應 POST 請求,提交指定資源的實體。
  • rest.put():對應 PUT 請求,上傳或取代指定的資源。
  • rest.patch():對應 PATCH 請求,指定資源的部份修改。
  • rest.delete():對應 DELETE 請求,刪除指定資源。
  • rest.options():對應 OPTIONS 請求,查詢 server 支援的 HTTP 請求方式。

這些方法需要固定傳入兩個參數-

  • entrypoint-API 的路徑,可以 /:someData 的方式以路徑傳入資料參數。
  • resolver function-請求完成後的 resolve 函式。有三個參數可以操作,分別是-
  1. reqreq.body 的物件可以取得 Request body 中的資料參數。
  2. res:執行 res() 來回覆請求。
  3. ctx:執行 ctx.json({…}) 組合回傳的資料,定義想要的格式和內容。

<p>CODE:https://gist.github.com/ACbosong/dd70f7a1e6acb8d12cf0977be7a901a3.js</p>

什麼是REST? 認識 RESTful API 路由語義化設計風格

GraphQL

從 msw 匯入 graphql這個模組提供了兩種操作方式

  • graphql.query():對應 GraphQL 的 Query 來完成資源的查詢。
  • graphql.mutation():對應 GraphQL 的 Mutation 來完成資源新增、刪除、修改等會產生副作用的操作。

這些方法需要固定傳入兩個參數:

  • name-已經定義好的 Query 或 Mutation 名稱。
  • resolver function-請求完成後的 resolve 函式。有三個參數可以操作,分別是-
  1. reqreq.variables 的物件可以取得 Request 中的資料參數。
  2. res:執行 res() 來回覆請求。
  3. ctx:執行 ctx.data({…}) 組合回傳的資料,根據定義好的欄位格式,設計想要的內容。

<p>CODE:https://gist.github.com/ACbosong/ae9b2289b1f61586f8a71fb356c294f3.js</p>

當我們寫好 handlers 後,在 browser.ts(js) 中就可以新增負責初始化 MSW 的變數,並且傳入 handlers 作為初始化的參數, 讓 MSW 知道遇到哪些請求後要執行 mock API 的動作。

<p>CODE:https://gist.github.com/ACbosong/93a948e0529906db4cbab5449413a1d6.js</p>

通常我們會在本機的開發環境,或是執行測試時,才會希望啟動 MSW。因此我們可以在主程式的進入點加上環境的條件判斷。

<p>CODE:https://gist.github.com/ACbosong/f13be981f08cfa255031ff4828fcd4cf.js</p>

小結

針對不同專案找出最適合的 API 設計,以及讓前端可以在面對各種資料情況時,都能提供良好的介面與瀏覽體驗。這些事情都可以透過 Mock API 的過程中實現。希望透過今天的介紹,讓大家對於 Mock API 有更深的了解,並且感受 MSW 擁有的魅力與好處