模擬 fetch()

使用 fetch-mock 為 Storybook.js 新增 fetch() 模擬功能

在 Github 上檢視

storybook-addon-fetch-mock

Storybook.js 附加元件使用 fetch-mock 新增 fetch() 模擬功能。

Node.js CI Status

為何要使用 storybook-addon-fetch-mock?

如果您已經在使用 Storybook.js,您的元件可能需要呼叫 API 端點。為了確保您的元件 Storybook 文件不依賴於這些可用的 API 端點,您會需要模擬對這些 API 端點的任何呼叫。如果您的任何元件會變更端點上的資料,則更是如此。

幸運的是,Storybook 生態系統有許多附加元件可以讓模擬 Fetch API 更容易。這些附加元件都允許您攔截元件中的實際 API 呼叫,並返回您想要的任何模擬資料回應。

Storybook 附加元件 完整的 Fetch 模擬 模擬函式 模擬物件
模擬服務工作人員附加元件 ✅ 2
模擬 API 請求附加元件 ❌ 1
storybook-addon-fetch-mock ✅ 3

1 如果您使用 XMLHttpRequest (XHR),模擬 API 請求附加元件會很適合您,但不建議用於模擬 Fetch API。其功能非常基本,而且某些 Fetch API 請求無法使用此附加元件模擬。

2 如果您想要模擬自己正在撰寫的 Fetch API,使用模擬服務工作人員附加元件撰寫模擬解析器函式可能會是最簡單的方法。

3 如果您想要模擬您撰寫的 Fetch API,撰寫簡單的 JavaScript 物件可能會是最簡單的模擬方法。此專案 storybook-addon-fetch-mock 是 fetch-mock 函式庫的輕量級封裝,該函式庫是一個維護良好、高度可配置的模擬函式庫,自 2015 年起可用。它允許您將模擬寫成簡單的 JavaScript 物件、解析器函式或兩者的組合。

快速範例

假設有一個 UnicornSearch 元件使用 fetch() 呼叫端點以搜尋獨角獸清單。您可以使用 storybook-addon-fetch-mock 來繞過實際的 API 端點並返回模擬回應。在遵循下方的「安裝」指示之後,您可以這樣設定 UnicornSearch.stories.js

import UnicornSearch from './UnicornSearch';

export default {
  title: 'Unicorn Search',
  component: UnicornSearch,
};

// We define the story here using CSF 3.0.
export const ShowMeTheUnicorns = {
  args: {
    search: '',
  },
  parameters: {
    fetchMock: {
      // "fetchMock.mocks" is a list of mocked
      // API endpoints.
      mocks: [
        {
          // The "matcher" determines if this
          // mock should respond to the current
          // call to fetch().
          matcher: {
            name: 'searchSuccess',
            url: 'path:/unicorn/list',
            query: {
              search: 'Charlie',
            },
          },
          // If the "matcher" matches the current
          // fetch() call, the fetch response is
          // built using this "response".
          response: {
            status: 200,
            body: {
              count: 1,
              unicorns: [
                {
                  name: 'Charlie',
                  location: 'magical Candy Mountain',
                },
              ],
            },
          },
        },
        {
          matcher: {
            name: 'searchFail',
            url: 'path:/unicorn/list',
          },
          response: {
            status: 200,
            body: {
              count: 0,
              unicorns: [],
            },
          },
        },
      ],
    },
  },
};

如果我們在 Storybook 中開啟「Show Me The Unicorns」故事,我們可以在「search」欄位中填入「Charlie」,假設 UnicornSearch 呼叫 fetch()https://example.com/unicorn/list?search=charlie,我們的 Storybook 附加元件會比較 parameters.fetchMock.mocks 中的每個模擬,直到找到符合的項目,並返回第一個模擬的回應。

如果我們在「search」欄位中填入不同的值,我們的 Storybook 附加元件會返回第二個模擬的回應。

安裝

  1. 將附加元件安裝為開發相依性

    npm i -D storybook-addon-fetch-mock
    
  2. 將附加元件的名稱新增到 .storybook/main.js 中的 addons 陣列,以註冊 Storybook 附加元件

    module.exports = {
      addons: ['storybook-addon-fetch-mock'],
    };
    
  3. 您可以選擇將 fetchMock 項目新增到 .storybook/preview.js 中的 parameters 物件,以設定附加元件。詳細資訊請參閱下方的「為所有故事設定全域參數」章節。

  4. 將模擬資料新增到您的故事。詳細資訊請參閱下方的「設定模擬資料」章節。

設定模擬資料

若要攔截對 API 端點的 fetch 呼叫,請新增一個包含一個或多個端點模擬的 parameters.fetchMock.mocks 陣列。

參數應該放在哪裡?

如果您將 parameters.fetchMock.mocks 陣列放在單一故事的匯出中,模擬只會套用到該故事

export const MyStory = {
  parameters: {
    fetchMock: {
      mocks: [
        // ...mocks go here
      ],
    },
  },
};

如果您將 parameters.fetchMock.mocks 陣列放在 Storybook 檔案的 default 匯出中,模擬會套用到該檔案中的所有故事。但是,如果您需要,您仍然可以覆寫每個故事的模擬。

export default {
  title: 'Components/Unicorn Search',
  component: UnicornSearch,
  parameters: {
    fetchMock: {
      mocks: [
        // ...mocks go here
      ],
    },
  },
};

您也可以將 parameters.fetchMock.mocks 陣列放在 Storybook 的 preview.js 設定檔中,但不建議這樣做。如需更好的替代方案,請參閱下方的「為所有故事設定全域參數」章節。

parameters.fetchMock.mocks 陣列

當呼叫 fetch() 時,會將 parameters.fetchMock.mocks 陣列中的每個模擬與 fetch() 請求進行比較,直到找到符合的項目。

每個模擬都應該是一個包含以下可能索引鍵的物件

  • matcher (必要):每個模擬的 matcher 物件都具有一個或多個用於比對的條件。如果 matcher 中包含多個條件,則所有條件都必須符合才能使用模擬。
  • response (選用):比對完成後,會使用符合的模擬的 response 來設定 fetch() 回應。
    • 如果模擬未指定 response,則 fetch() 回應會使用沒有主體資料的 HTTP 200 狀態。
    • 如果 response 是一個物件,則會使用這些值來建立 fetch() 回應。
    • 如果 response 是一個函式,則該函式應該傳回一個物件,該物件的值會用於建立 fetch() 回應。
  • options (選用):用於設定模擬行為的更多選項。

以下是 matcherresponseoptions 的所有可能索引鍵的完整清單

const exampleMock = {
  // Criteria for deciding which requests should match this
  // mock. If multiple criteria are included, all of the
  // criteria must match in order for the mock to be used.
  matcher: {
    // Match only requests where the endpoint "url" is matched
    // using any one of these formats:
    // - "url" - Match an exact url.
    //     e.g. "http://www.site.com/page.html"
    // - "*" - Match any url
    // - "begin:..." - Match a url beginning with a string,
    //     e.g. "begin:http://www.site.com"
    // - "end:..." - Match a url ending with a string
    //     e.g. "end:.jpg"
    // - "path:..." - Match a url which has a given path
    //     e.g. "path:/posts/2018/7/3"
    // - "glob:..." - Match a url using a glob pattern
    //     e.g. "glob:http://*.*"
    // - "express:..." - Match a url that satisfies an express
    //     style path. e.g. "express:/user/:user"
    // - RegExp - Match a url that satisfies a regular
    //     expression. e.g. /(article|post)\/\d+/
    url: 'https://example.com/endpoint/search',

    // If you have multiple mocks that use the same "url",
    // a unique "name" is required.
    name: 'searchSuccess',

    // Match only requests using this HTTP method. Not
    // case-sensitive.
    method: 'POST',

    // Match only requests that have these headers set.
    headers: {
      Authorization: 'Basic 123',
    },

    // Match only requests that send a JSON body with the
    // exact structure and properties as the one provided.
    // See matcher.matchPartialBody below to override this.
    body: {
      unicornName: 'Charlie',
    },

    // Match calls that only partially match the specified
    // matcher.body JSON.
    matchPartialBody: true,

    // Match only requests that have these query parameters
    // set (in any order).
    query: {
      q: 'cute+kittenz',
    },

    // When the express: keyword is used in the "url"
    // matcher, match only requests with these express
    // parameters.
    params: {
      user: 'charlie',
    },

    // Match if the function returns something truthy. The
    // function will be passed the url and options fetch was
    // called with. If fetch was called with a Request
    // instance, it will be passed url and options inferred
    // from the Request instance, with the original Request
    // will be passed as a third argument.
    functionMatcher: (url, options, request) => {
      return !!options.headers.Authorization;
    },

    // Limits the number of times the mock can be matched.
    // If the mock has already been used "repeat" times,
    // the call to fetch() will fall through to be handled
    // by any other mocks.
    repeat: 1,
  },

  // Configures the HTTP response returned by the mock.
  response: {
    // The mock response’s "statusText" is automatically set
    // based on this "status" number. Defaults to 200.
    status: 200,

    // By default, the optional "body" object will be converted
    // into a JSON string. See options.sendAsJson to override.
    body: {
      unicorns: true,
    },

    // Set the mock response’s headers.
    headers: {
      'Content-Type': 'text/html',
    },

    // The url from which the mocked response should claim
    // to originate from (to imitate followed directs).
    // Will also set `redirected: true` on the response.
    redirectUrl: 'https://example.com/search',

    // Force fetch to return a Promise rejected with the
    // value of "throws".
    throws: new TypeError('Failed to fetch'),
  },

  // Alternatively, the `response` can be a function that
  // returns an object with any of the keys above. The
  // function will be passed the url and options fetch was
  // called with. If fetch was called with a Request
  // instance, it will be passed url and options inferred
  // from the Request instance, with the original Request
  // will be passed as a third argument.
  response: (url, options, request) => {
    return {
      status: options.headers.Authorization ? 200 : 403,
    };
  },

  // An object containing further options for configuring
  // mocking behaviour.
  options: {
    // If set, the mocked response is delayed for the
    // specified number of milliseconds.
    delay: 500,

    // By default, the "body" object is converted to a JSON
    // string and the "Content-Type: application/json"
    // header will be set on the mock response. If this
    // option is set to false, the "body" object can be any
    // of the other types that fetch() supports, e.g. Blob,
    // ArrayBuffer, TypedArray, DataView, FormData,
    // URLSearchParams, string or ReadableStream.
    sendAsJson: false,

    // By default, a Content-Length header is set on each
    // mock response. This can be disabled when this option
    // is set to false.
    includeContentLength: false,
  },
};

為所有故事設定全域參數

以下選項旨在用於 Storybook 的 preview.js 設定檔中。

// .storybook/preview.js
export const parameters = {
  fetchMock: {
    // When the story is reloaded (or you navigate to a new
    // story, this addon will be reset and a list of
    // previous mock matches will be sent to the browser’s
    // console if "debug" is true.
    debug: true,

    // Do any additional configuration of fetch-mock, e.g.
    // setting fetchMock.config or calling other fetch-mock
    // API methods. This function is given the fetchMock
    // instance as its only parameter and is called after
    // mocks are added but before catchAllMocks are added.
    useFetchMock: (fetchMock) => {
      fetchMock.config.overwriteRoutes = false;
    },

    // After each story’s `mocks` are added, these catch-all
    // mocks are added.
    catchAllMocks: [
      { matcher: { url: 'path:/endpoint1' }, response: 200 },
      { matcher: { url: 'path:/endpoint2' }, response: 200 },
    ],

    // A simple list of URLs to ensure that calls to
    // `fetch( [url] )` don’t go to the network. The mocked
    // fetch response will use HTTP status 404 to make it
    // easy to determine one of the catchAllURLs was matched.
    // These mocks are added after any catchAllMocks.
    catchAllURLs: [
      // This is equivalent to the mock object:
      // {
      //   matcher: { url: 'begin:http://example.com/' },
      //   response: { status: 404 },
      // }
      'http://example.com/',
    ],
  },
};
  • johnalbin
    johnalbin
適用於
    Angular
    Ember
    HTML
    Preact
    React
    React native
    Svelte
    Vue
    Web Components
標籤