Chapter 08

非同期リクエストを扱うコンポーネントのテスト:リクエストをインターセプト編

MSW でモックサーバが起動しているかのように

MSW はどういったものかというと、リクエストをインターセプトしレスポンスをジャックします。window.fetch がリクエストを本当のエンドポイントへ向ける前に横取りするのです。

いわゆるモック用の HTTP サーバが起動するわけではなく、Node.js 実行環境化であれば https.get, https.request をジャックし外部へのリクエストが発生することなくクライアントにレスポンスを返却します。

ブラウザでも動作させることは可能で ServiceWorker から起動しリクエストをインターセプトする機能を持っています。ですので開発時のモックサーバ(のような扱い)としても利用できるのです。

MSW の使い方

MSW 起動や特定の振る舞いを書くことになるハンドラは、Express のルートハンドラを書いたことがある開発者なら馴染みのある書き味でまさにモックサーバのように記述できます。

先に今回必要なハンドラを書きます。REST, GraphQL いずれにも対応可能な API を用意しており今回は graphql を使って実装します。

handlers.ts
export const handlers = [
  // Query に `PokemonList` といったオペレーション名がついてればこのハンドラが動きます
  // コールバックに指定された req, res の引数はよく見るものになっています
  graphql.query("PokemonList", (req, res, ctx) => {
    // variables はクエリのパラメータ、req オブジェクトにはよく見るような
    // headers, cookies なども取得できるようになっています
    const { size } = req.variables
    return res(
      ctx.data({
        /** ここにレスポンスを記述する */
      })
    )
  })
]

モックサーバを起動する記述はあまりおもしろくないのでここでは割愛しましょう。

テストへの組込み方

これらはサーバであるかのように振る舞うと同時に起動についても類似したような記述をすることになります。ここではテストファイルの Setup/Teardown で各テストケースで前提条件が変わらないよう記述します。

beforeAll(() => {
  server.listen()
})
afterEach(() => {
  server.resetHandlers()
})
afterAll(() => {
  server.close()
})

fetch の代替

さらに window.fetch をモックしないということは、前章で取り上げたように global.fetch を実装しないため、fetch はどこにも存在しないことになります。

そこで Jest がセットアップする際に fetch が動作するかのように node-fetch に振る舞ってもらうことにしましょう。

// fetch がなければ node-fetch で代替
if (!globalThis.fetch) {
  globalThis.fetch = fetch
}

node-fetch が実装する fetch は Web API と最小限の互換性を持っており、さらには MSW がジャックする Node.js http, https モジュールを利用して実装されているのです。MSW によって fetch をジャックする準備が整いました。

テストケースやテストするポイントは変わらないため、前章のテストコードにどういった差分が出てくるか見ていくことにしましょう。

MSW を導入することで前提条件が不要になる

以下のエディタのスクリーンショットはモックを用意していたのが左のペインで、MSW に置き換えた後が右のペインです。

テストケースごとにモックで window.fetch を代替していた記述が減ったこともさることながら、全体的にモックが削除されたことでテストケースは「どのようにユーザーに見えて操作されるか」をはっきりと主張できているように感じます。

バリエーション:Apollo, React Query

テストコードや実装コードをここに記載しませんが、Apollo Clientを採用したケースにおける MockedProvider の使い方も今回リポジトリに含めています。

モックを自分で作ることがないので API からのレスポンスだけを意識して書ききることができます。

React Query を利用したコンポーネントやテストコードについては特に代わり映えしませんが、Hooks として切り出した実装の中で fetch を利用しています。そのため本章と同じく MSW を使ったテストコードをリポジトリには配置しました。


次章ではコンポーネント間をまたいだ Context があるケースでどのようにテストを書くか、Context を扱うカスタム Hooks のテストはどう書いていくか見ていきましょう。