Mock Service Worker

使用 Mock Service Worker 在 Storybook 中模擬 API 請求。

在 Github 上查看

功能

  • 直接在您的 story 中模擬 Rest 和 GraphQL 請求。
  • 記錄元件在各種情境下的行為方式。
  • 免費使用其他擴充套件取得 a11y、快照和視覺測試。

完整文件和即時演示

安裝與設定

安裝 MSW 和擴充套件

使用 npm

npm i msw msw-storybook-addon -D

或使用 yarn

yarn add msw msw-storybook-addon -D

在您的 public 資料夾中為 MSW 產生 service worker。

如果您已經在您的專案中使用 MSW,您可能已經完成此步驟,因此可以跳過此步驟。

npx msw init public/

如果您不使用 public,請參閱 MSW 官方指南 以了解特定框架的路徑。

設定擴充套件

透過初始化 MSW 並在 ./storybook/preview.js 中提供 MSW 載入器,在 Storybook 中啟用 MSW

import { initialize, mswLoader } from 'msw-storybook-addon'

// Initialize MSW
initialize()

const preview = {
  parameters: {
    // your other code...
  },
  // Provide the MSW addon loader globally
  loaders: [mswLoader],
}

export default preview

啟動 Storybook

執行 Storybook 時,您必須將 public 資料夾作為資源提供給 Storybook,以便包含 MSW,否則它將無法在瀏覽器中使用。

這表示您應該在 Storybook 主要設定檔中設定 staticDirs 欄位。如果需要,請參閱文件

npm run storybook

用法

您可以將請求處理器 (https://mswjs.io/docs/concepts/request-handler) 傳遞到 msw 參數的 handlers 屬性中。這通常是一個處理器陣列。

import { http, HttpResponse } from 'msw'

export const SuccessBehavior = {
  parameters: {
    msw: {
      handlers: [
        http.get('/user', () => {
          return HttpResponse.json({
            firstName: 'Neil',
            lastName: 'Maverick',
          })
        }),
      ],
    },
  },
}

進階用法

組合請求處理器

handlers 屬性也可以是一個物件,其中鍵值可以是處理器陣列或處理器本身。這讓您可以使用參數繼承從 preview.js 繼承(並選擇性地覆寫/停用)處理器

type MswParameter = {
  handlers: RequestHandler[] | Record<string, RequestHandler | RequestHandler[]>
}

假設您有一個應用程式,其中幾乎每個元件都需要以相同的方式模擬對 /login/logout 的請求。您可以在 preview.js 中為這些請求設定全域 MSW 處理器,並將它們捆綁到一個名為 auth 的屬性中,例如

//preview.ts
import { http, HttpResponse } from 'msw'

// These handlers will be applied in every story
export const parameters = {
  msw: {
    handlers: {
      auth: [
        http.get('/login', () => {
          return HttpResponse.json({
            success: true,
          })
        }),
        http.get('/logout', () => {
          return HttpResponse.json({
            success: true,
          })
        }),
      ],
    },
  },
}

然後,您可以在個別的 story 中使用其他處理器。Storybook 將合併全域處理器和 story 處理器

import { http, HttpResponse } from 'msw'

// This story will include the auth handlers from .storybook/preview.ts and profile handlers
export const SuccessBehavior = {
  parameters: {
    msw: {
      handlers: {
        profile: http.get('/profile', () => {
          return HttpResponse.json({
            firstName: 'Neil',
            lastName: 'Maverick',
          })
        }),
      },
    },
  },
}

現在假設您想要覆寫 auth 的全域處理器。您只需在您的 story 中再次設定它們,這些值將優先

import { http, HttpResponse } from 'msw'

// This story will overwrite the auth handlers from preview.ts
export const FailureBehavior = {
  parameters: {
    msw: {
      handlers: {
        auth: http.get('/login', () => {
          return HttpResponse.json(null, { status: 403 })
        }),
      },
    },
  },
}

如果您想要停用全域處理器呢?您只需將它們設定為 null,它們將在您的 story 中被忽略

import { http, HttpResponse } from 'msw'

// This story will disable the auth handlers from preview.ts
export const NoAuthBehavior = {
  parameters: {
    msw: {
      handlers: {
        auth: null,
        others: [
          http.get('/numbers', () => {
            return HttpResponse.json([1, 2, 3])
          }),
          http.get('/strings', () => {
            return HttpResponse.json(['a', 'b', 'c'])
          }),
        ],
      },
    },
  },
}

設定 MSW

msw-storybook-addon 使用預設設定啟動 MSW。initialize 接受兩個參數

一個常見的範例是設定 onUnhandledRequest 行為,因為在有未處理的請求時,MSW 會記錄警告。

如果您希望 MSW 略過未處理的請求而不做任何事

// .storybook/preview.ts
import { initialize } from 'msw-storybook-addon'

initialize({
  onUnhandledRequest: 'bypass',
})

如果您想要在 story 發出應該處理但未處理的請求時發出有用的警告訊息

// .storybook/preview.ts
import { initialize } from 'msw-storybook-addon'

initialize({
  onUnhandledRequest: ({ url, method }) => {
    const pathname = new URL(url).pathname
    if (pathname.startsWith('/my-specific-api-path')) {
      console.error(`Unhandled ${method} request to ${url}.

        This exception has been only logged in the console, however, it's strongly recommended to resolve this error as you don't want unmocked data in Storybook stories.

        If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses
      `)
    }
  },
})

雖然可以組合處理器,但這取決於 Storybook 的合併邏輯,**只有當您的 story 參數中的處理器是物件而不是陣列時才有效**。為了繞過此限制,您可以將初始請求處理器直接作為第二個參數傳遞給 initialize 函式。

// .storybook/preview.ts
import { http, HttpResponse } from 'msw'
import { initialize } from 'msw-storybook-addon'

initialize({}, [
  http.get('/numbers', () => {
    return HttpResponse.json([1, 2, 3])
  }),
  http.get('/strings', () => {
    return HttpResponse.json(['a', 'b', 'c'])
  }),
])

在 Node.js 中使用具有可攜式 Story 的擴充套件

如果您正在使用可攜式 story,您需要確保正確應用 MSW 載入器。

Storybook 8.2 或更高版本

如果您正確設定專案註解,藉由呼叫您 story 的 play 函式,MSW 載入器將會自動應用

import { composeStories } from '@storybook/react'
import * as stories from './MyComponent.stories'

const { Success } = composeStories(stories)

test('<Success />', async() => {
  // The MSW loaders are applied automatically via the play function
  await Success.play()
})

Storybook < 8.2

您可以在呈現您的 story 之前呼叫 applyRequestHandlers 輔助程式來執行此操作

import { applyRequestHandlers } from 'msw-storybook-addon'
import { composeStories } from '@storybook/react'
import * as stories from './MyComponent.stories'

const { Success } = composeStories(stories)

test('<Success />', async() => {
  // 👇 Crucial step, so that the MSW loaders are applied
  await applyRequestHandlers(Success.parameters.msw)
  render(<Success />)
})

注意:applyRequestHandlers 公用程式應該是一個由可攜式 story 自動呼叫的內部詳細資訊,但是由於在 Storybook 7 中無法實現,因此由擴充套件匯出。它將在即將發行的版本中移除,因此建議您盡可能升級到 Storybook 8。

疑難排解

MSW 正在干擾 HMR (熱模組更換)

如果您在主控台中遇到類似 [MSW] Failed to mock a "GET" request to "https://127.0.0.1:6006/4cb31fa2eee22cf5b32f.hot-update.json" 的問題,可能是 MSW 正在干擾 HMR。這並不常見,而且似乎只會在 Webpack 專案中發生,但如果發生在您身上,您可以按照此問題中的步驟來修正它

https://github.com/mswjs/msw-storybook-addon/issues/36#issuecomment-1496150729