外部へのネットワークリクエストを含むフロントエンドのコンポーネントの開発やテストを実施する場合に実際にリクエスト先のサーバにネットワークリクエストを送信するのではなくMockを利用する場合があります。Mock Service Worker(MSW)を利用するとネットワークリクエストをInterceptして事前にMSWで設定していたデータをレスポンスとしてコンポーネントに戻すことができます。そのためコンポーネントから見ると実際にネットワークリクエストをサーバに送信し、サーバからレスポンスが戻っているように見えます。バックエンドサーバが存在しない場合でもフロントエンドの開発を進めることができます。

Mock Service Workerという名前の一部にService Workerという名前が入っているためService Workerの事前知識が必要なのではと心配になる人もいるかと思います。しかしMSWではブラウザにおけるネットワークリクエストをInterceptするためのService Workerに関するコードが用意されているためService Workerの知識は全く必要ありません。さらにService Workerを必ず利用するわけではありません。MSWはブラウザ環境とNode.js環境で利用することができ、Node.js環境で利用する場合にはService Workerは利用しません。

本文書を通してブラウザとNode.jsにおけるMSW設定方法と利用方法を同時に理解することができます。ブラウザ、Node.js環境で利用するということはどういうことなのかという疑問についても解決できるはずです。REST APIだけではなくGraphQL APIにも対応していますが動作確認にはREST APIを利用しています。VueとReactの環境で動作確認しているので確認したい環境を選択して読み進めてください。どちらもViteを利用しており、テストにはVitestを利用しています。

環境の構築

Vueプロジェクトの作成

Vueプロジェクトの作成はnpm init vueコマンドを利用して行います。コマンドを実行すると対話式に利用する機能の選択を行う必要がありますがここでは”Vitest for Unit Testing”のみ”Yes”を選択して後は”No”を選択しています。プロジェクトの名前は任意の名前をつけることができるので”vue-msw”としています。


 npm init vue@latest
Need to install the following packages:
  create-vue@3.6.1
Ok to proceed? (y) y

Vue.js - The Progressive JavaScript Framework

✔ Project name: … vue-msw
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … No / Yes

Scaffolding project in /Users/mac/Desktop/vue-msw...

Done. Now run:

  cd vue-msw
  npm install
  npm run dev

プロジェクト作成後、プロジェクトフォルダに移動してnpm insrtallコマンドを実行してください。


 % cd vue-msw
 % npm install

MSWのインストール

MSWのインストールを行います。


 % npm install msw --save-dev

package.jsonファイルの中身を確認して利用しているライブラリのバージョンを確認しておきます。


{
  "name": "vue-msw",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "test:unit": "vitest"
  },
  "dependencies": {
    "vue": "^3.2.47"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^4.0.0",
    "@vue/test-utils": "^2.3.0",
    "jsdom": "^21.1.0",
    "msw": "^1.1.0",
    "vite": "^4.1.4",
    "vitest": "^0.29.1"
  }
}

コンポーネントを作成

MSWを利用するコンポーネントファイルUserList.vueファイルを作成します。無料で利用することができるJSONPLaceHolderからfetch関数を利用してユーザ情報を取得してv-forディレクティブを利用して展開しています。


<script setup>
import { ref } from 'vue';

const users = ref([]);

const getUsers = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  const data = await response.json();
  users.value = data;
};

getUsers();
</script>
<template>
  <h1>ユーザ一覧</h1>
  <ul>
    <li v-for="user in users" :key="user.id">{{ user.id }}.{{ user.name }}</li>
  </ul>
</template>

App.vueファイルでは作成したUserListコンポーネントをimportしています。


<script setup>
import UserList from './components/UserList.vue';
</script>

<template>
  <main style="margin: 1em">
    <UserList />
  </main>
</template>

main.jsファイルではデフォルトではassetsフォルダのmain.cssファイルをimportしていますが利用しないのでimport文をコメントアウトしておきます。


import { createApp } from 'vue';
import App from './App.vue';

// import './assets/main.css';

createApp(App).mount('#app');

MSWの設定を行っていないのでnpm run devコマンドで開発サーバを起動するとユーザ一覧が表示されます。

ユーザ一覧の設定
ユーザ一覧の設定

MSWの設定(Vue.js)

MSWのインストールは完了しているのでここからMSWの設定を行なっていきます。srcフォルダの下にmocksフォルダを作成してその中にhandlers.jsファイルを作成します。handlers.jsファイルの中ではrest.メソッド名を利用しrequest handlerを設定します。


import { rest } from 'msw';

export const handlers = [
  rest.get('https://jsonplaceholder.typicode.com/users', (req, res, ctx) => {
    return res(ctx.status(200), ctx.json([{ id: 1, name: 'John' }]));
  }),
];

https://jsonplaceholder.typicode.com/usersに対するGETリクエストのハンドラーを設定しており、第一引数にはリクエスト先のURLを設定し、第二引数にはResponse resolver関数を設定します。Respon resolver関数はreq(Request), res(Response), ctx(context)の3つの引数を取り、ctxではステータスコードやResponseとして戻すデータを設定することができます。ここではステータスコード200、JSONデータでユーザの配列情報を戻しています。

ブラウザで利用する場合

MSWはブラウザ上で利用するかNode.js上で利用するかでここからの設定が異なります。最初はブラウザ上で利用するためService Workerの設定を行います。

Vitestを利用してテストを実施する際はNode.jsでテストを実行するためService Workerを利用することはできないので設定方法が異なります。

Service Workerとして利用するためには動作するためのコードが必要となります。 コードはライブラリが事前に用意してくれているのでそのファイルを利用します。コマンドを利用することで指定した場所にファイルのコピーが行われます。コピーを作成するために以下のコマンドを実行します。


 % npx msw init public/ --save

msw initの後のpublicは利用するフレームワークによって異なりますがVue.jsの場合はpublicフォルダが公開フォルダなのでpublicを指定します。

実行するとpublicフォルダにmockServiceWorker.jsファイルが作成されます。

次にsrc/mocksフォルダの下にbrowser.jsファイルを作成してworkerインスタンスを作成するコードを記述します。


import { setupWorker } from 'msw';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);

Service Workerの起動はmain.jsファイルで行います。MSWを利用するのは開発時のみなので環境変数の値でdevelopmentかproductionをチェックしています。developmentの場合のみ作成したbrowser.jsファイルをimportしてworkerを起動します。


import { createApp } from 'vue';
import App from './App.vue';

// import './assets/main.css';

if (import.meta.env.MODE === 'development') {
  const { worker } = await import('./mocks/browser');
  worker.start();
}

createApp(App).mount('#app');

開発サーバを起動してブラウザを確認するとMSWのrequest handlerで設定したユーザ情報が表示されていることが確認できます。ブラウザのデベロッパーツールを確認するとMSWに関するメッセージが表示されていることがわかります。

MSWから戻されたデータを表示
MSWから戻されたデータを表示

ネットワークタブで確認すると”https://jsonplaceholder.typicode.com/users”にアクセスが行わわれていますがStatus Codeを見るとservice workerから戻されているものだとわかります。

ネットワークタブの確認
ネットワークタブの確認

MSWを利用しなかった場合のリクエストも確認しておきます。Status Codeにはservice workerという文字列は見つけられずRemote Addressが表示されています。

MSWを利用しなかった場合
MSWを利用しなかった場合

ここまでの動作確認で”Mock Service Worker(MSW)でリクエストをInterceptして事前にMSWで設定していたデータをコンポーネントに戻すことができます”という意味も理解できたのではないでしょうか。

ApplicationタブのService Workersを確認すると登録されたService Workersの情報を確認することができます。

ApplicationタブのService Workersを確認
ApplicationタブのService Workersを確認

URLを相対パスにした場合

JSONPlaceHolderを利用した場合はhttps://から始まるURLを設定していましたローカルサーバ上の/api/usersにアクセスする場合にMSWでは相対パスが利用できるのか確認します。

UserList.vueファイルのfetch関数の引数に設定するURLをhttps://…から/api/usersに変更します。


const getUsers = async () => {
  // const response = await fetch('https://jsonplaceholder.typicode.com/users');
  const response = await fetch('/api/users');
  const data = await response.json();
  users.value = data;
};

handlers.jsのrest.getの引数の値をfetch関数の引数のURLに合わせます。


import { rest } from 'msw';

export const handlers = [
  // rest.get('https://jsonplaceholder.typicode.com/users', (req, res, ctx) => {
  rest.get('/api/users', (req, res, ctx) => {
    return res(ctx.status(200), ctx.json([{ id: 1, name: 'John' }]));
  }),
];

ブラウザを利用して動作確認を行うと/api/usersの相対パスでも動作することがわかります。Node.jsでは相対パスでは動作しないことを確認します。

MSWが動作しない場合

MSWを設定するとブラウザのデベロッパーツールのコンソールにメッセージ”[MSW] Mocking enabled.”が表示されることを確認しました。”Mocking enabled”のメッセージはService Workerの起動が完了してMockが利用できる状態になった時に出力されますがService Workderの起動よりも先にリクエストが行われる場合があります。”Mocking enabled”になる前にリクエストが行われるとMockを利用しないまま処理が行われます。Service Workerの起動を行ってからコンポーネントのマウント処理を行うように下記のようにPromiseを利用します。


import { createApp } from 'vue';
import App from './App.vue';

// import './assets/main.css';

async function prepare() {
  if (import.meta.env.MODE === 'development') {
    const { worker } = await import('./mocks/browser');
    worker.start();
  }
  return Promise.resolve();
}

prepare().then(() => {
  createApp(App).mount('#app');
});

Node.jsで利用する場合

プロジェクトの作成時にVitest for Unit Testingを選択している場合にはVitestがインストールされておりすぐにUnit Testを開始することができます。componentsフォルダには__test__フォルダが作成されその中にHelloWorld.spec.jsファイルがあります。package.jsonファイルのscriptsにtest:unitが登録されているのでnpm run test:unitコマンドを実行するとテストが実行されます。


"scripts": {
  "dev": "vite",
  "build": "vite build",
  "preview": "vite preview",
  "test:unit": "vitest"
},

サーバの設定

VitestはNode上で実行されるためService Workerを利用することができません。そのためブラウザの場合と設定方法が一部異なりService Workerの代わりにServerの設定を行います。

src/mocksフォルダにserver.jsファイルを作成して以下のコードを記述します。


import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)

一見するとbrowser.jsファイルとの違いがわからないかもしれませんがブラウザの場合(browser.js)にmswからsetupWorkerをimportしていましたがNodeの場合(server.js)はmsw/nodeからsetupServerをimportしています。

handlers.jsファイルについてはブラウザとNodeのどちらでも利用することができます。


import { rest } from 'msw';

export const handlers = [
  rest.get('https://jsonplaceholder.typicode.com/users', (req, res, ctx) => {
    return res(ctx.status(200), ctx.json([{ id: 1, name: 'John' }]));
  }),
];

テストコード

MSWによるサーバの設定が完了したのでテストコードを作成します。components/__test__フォルダにUserList.spec.jsファイルを作成して以下のコードを記述します。


import { describe, it, expect, beforeAll, afterEach, afterAll } from 'vitest';
import { server } from '../../mocks/server';
import { mount, flushPromises } from '@vue/test-utils';
import UserList from '../UserList.vue';

beforeAll(() => server.listen());

afterEach(() => server.resetHandlers());

afterAll(() => server.close());

describe('UserList', () => {
  it('renders properly', async () => {
    const wrapper = mount(UserList);
    await flushPromises();
    expect(wrapper.text()).toContain('John');
  });
});

MSWのサーバの起動はserver.listen()で開始されます。server.listen()によりrequest interceptionが行えるようになります。テストが完了したらserver.close()でrequest interceptionを停止してクリーンアップします。各テストが完了するとresetHandlersでhandlerのリセットを行っています。

mount関数でUserListコンポーネントを実行した後にflushPromisesでfetch関数の処理が完了するのを待ってexpect関数を実行します。


 % npm run test:unit
 
 DEV  v0.29.2 /Users/mac/Desktop/vue-msw

 ✓ src/components/__tests__/HelloWorld.spec.js (1)
 ✓ src/components/__tests__/UserList.spec.js (1)

 Test Files  2 passed (2)
      Tests  2 passed (2)
   Start at  23:50:28
   Duration  1.23s (transform 133ms, setup 0ms, collect 461ms, tests 63ms)


 PASS  Waiting for file changes...
       press h to show help, press q to quit

実行するとデフォルトで存在するHelloWorld.spec.jsファイルによるテストも行われため2件のテストが実行され2件ともパスすることが確認できます。

flushPromisesがない場合はfetch関数でデータを取得してリスト表示する前にexpect関数が実行されるためテストに失敗します。

実際にコンポーネントのDOMの内容を確認したい場合にはawait flushPromisesの後にconsole.log(wrapper.text())の行を追加してください。

URLを相対パスにした場合

JSONPlaceHolderを利用した場合はhttps://から始まるURLを設定していましたがローカルサーバ上の/api/usersにアクセスする場合にMSWでは相対パスが利用できるのか確認します。

UserList.vueファイルのfetch関数の引数に設定するURLをhttps://…から/api/usersに変更します。


const getUsers = async () => {
  // const response = await fetch('https://jsonplaceholder.typicode.com/users');
  const response = await fetch('/api/users');
  const data = await response.json();
  users.value = data;
};

handlers.jsのrest.getの引数の値をfetch関数の引数のURLに合わせます。


import { rest } from 'msw';

export const handlers = [
  // rest.get('https://jsonplaceholder.typicode.com/users', (req, res, ctx) => {
  rest.get('/api/users', (req, res, ctx) => {
    return res(ctx.status(200), ctx.json([{ id: 1, name: 'John' }]));
  }),
];

ブラウザの場合とは異なりNodeでは以下のInvalid URLのメッセージが表示されテストに失敗します。


This error originated in "src/components/__tests__/UserList.spec.js" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.
Caused by: TypeError: Invalid URL: /api/users

Node.jsの場合には絶対パスでURL(https://….)を設定する必要があります。

環境の構築

Reactプロジェクトの作成

Reactプロジェクトの作成はviteを利用して作成するためnpm create viteコマンドを実行します。コマンドを実行するとプロジェクトの名前とフレームワークの選択とTypeScript, JavaScriptの選択を行うことができます。ここではフレームワークにReact、JavaScriptを選択しプロジェクト名はreact-mswにしています。プロジェクト名は任意の名前をつけることができます。


 % npm create vite@latest
✔ Project name: … react-msw
✔ Select a framework: › React
✔ Select a variant: › JavaScript

Scaffolding project in /Users/mac/Desktop/react-msw...

Done. Now run:

  cd react-msw
  npm install
  npm run dev

コマンド実行後に作成されるreact-mswフォルダに移動してnpm installコマンドを実行します。


 % cd react-msw
 % npm install

Viteでプロジェクトを作成した場合はデフォルトではテストツールのパッケージのインストールはおこなわれていません。テストを行うために必要となるVitestインストールを行います。


 % % npm install --save-dev vitest 
ブラウザでMSWを利用する場合にはテストツールをインストールする必要はありません。Node.js上でのUnit Testingなどを行う際にテストツールを利用します。

ReactコンポーネントをVitestでテストを行うためにはVitest本体以外にtesting-libraryとjsdomをインストールする必要があります。jsdomの代わりにhappy-domを利用することも可能です。


 % npm install --save-dev jsdom @testing-library/react

MSWのインストール

MSWのインストールを行います。


 % npm install msw --save-dev

package.jsonファイルの中身を確認して利用しているライブラリのバージョンを確認しておきます。


{
  "name": "react-msw",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@testing-library/react": "^14.0.0",
    "@types/react": "^18.0.27",
    "@types/react-dom": "^18.0.10",
    "@vitejs/plugin-react": "^3.1.0",
    "jsdom": "^21.1.0",
    "msw": "^1.1.0",
    "vite": "^4.1.0",
    "vitest": "^0.29.2"
  }
}

コンポーネントを作成

srcフォルダにcomponentsフォルダを作成してMSWを利用するコンポーネントファイルUserList.jsxファイルを作成します。無料で利用することができるJSONPLaceHolderからfetch関数を利用してユーザ情報を取得してmap関数を利用して展開しています。


import { useState, useEffect } from 'react';

const UserList = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const getUsers = async () => {
      const response = await fetch(
        'https://jsonplaceholder.typicode.com/users'
      );
      const data = await response.json();
      setUsers(data);
    };
    getUsers();
  }, []);

  return (
    <>
      <h1>ユーザ一覧</h1>
      <ul>
        {users.map((user) => (
          <li key={user.id}>
            {user.id}.{user.name}
          </li>
        ))}
      </ul>
    </>
  );
};

export default UserList;

ルートコンポーネントのApp.jsxファイルからUserListコンポーネントをimportします。


import UserList from './components/UserList';
function App() {
  return (
    <div>
      <UserList />
    </div>
  );
}

export default App;

main.jsxファイルではCSSの適用を解除するためにindex.cssのimoprtをコメントアウトします。


import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
// import './index.css'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

開発サーバを起動するためnpm run devコマンドを実行します。


 % npm run dev

> react-msw@0.0.0 dev
> vite


  VITE v4.1.4  ready in 504 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h to show help

ブラウザからhttp://localhost:5173にアクセスするとユーザ一覧が表示されます。

ユーザ一覧の表示
ユーザ一覧の表示

MSWの設定(React)

MSWのインストールは完了しているのでここからMSWの設定を行なっていきます。srcフォルダの下にmocksフォルダを作成してその中にhandlers.jsファイルを作成します。handlers.jsファイルの中ではrest.メソッド名を利用しrequest handlerを設定します。


import { rest } from 'msw';

export const handlers = [
  rest.get('https://jsonplaceholder.typicode.com/users', (req, res, ctx) => {
    return res(ctx.status(200), ctx.json([{ id: 1, name: 'John' }]));
  }),
];

https://jsonplaceholder.typicode.com/usersに対するGETリクエストのハンドラーを設定しており、第一引数にはリクエスト先のURLを設定し、第二引数にはResponse resolver関数を設定します。Respon resolver関数はreq(Request), res(Response), ctx(context)の3つの引数を取り、ctxではステータスコードやResponseとして戻すデータを設定することができます。ここではステータスコード200、JSONデータでユーザの配列情報を戻しています。

ブラウザで利用する場合

MSWはブラウザ上で利用するかNode.js上で利用するかでここからの設定が異なります。最初はブラウザ上で利用するためService Workerの設定を行います。

Vitestを利用してテストを実施する際はNode.jsでテストを実行するためService Workerを利用することはできないので設定方法が異なります。

Service Workerとして利用するためには動作するためのコードが必要となります。 コードはライブラリが事前に用意してくれているのでそのファイルを利用します。コマンドを利用することで指定した場所にファイルのコピーが行われます。コピーを作成するために以下のコマンドを実行します。


 % npx msw init public/ --save

msw initの後のpublicは利用するフレームワークによって異なりますがReactの場合はpublicフォルダが公開フォルダなのでpublicを指定します。

実行するとpublicフォルダにmockServiceWorker.jsファイルが作成されます。

次にsrc/mocksフォルダの下にbrowser.jsファイルを作成してworkerインスタンスを作成するコードを記述します。


import { setupWorker } from 'msw';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);

Service Workerの起動はmain.jsファイルで行います。MSWを利用するのは開発時のみなので環境変数の値でdevelopmentかproductionをチェックしています。developmentの場合のみ作成したbrowser.jsファイルをimportしてworkerを起動します。


import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
// import './index.css'

if (import.meta.env.MODE === 'development') {
  const { worker } = await import('./mocks/browser');
  worker.start();
}

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

開発サーバを起動してブラウザを確認するとMSWのrequest handlerで設定したユーザ情報が表示されていることが確認できます。ブラウザのデベロッパーツールを確認するとMSWに関するメッセージが表示されていることがわかります。

MSWから戻されたデータを表示
MSWから戻されたデータを表示

ネットワークタブで確認すると”https://jsonplaceholder.typicode.com/users”にアクセスが行わわれていますがStatus Codeを見るとservice workerから戻されているものだとわかります。

ネットワークタブの確認
ネットワークタブの確認

MSWを利用しなかった場合のリクエストも確認しておきます。Status Codeにはservice workerという文字列は見つけられずRemote Addressが表示されています。

MSWを利用しなかった場合
MSWを利用しなかった場合

ここまでの動作確認で”Mock Service Worker(MSW)でリクエストをInterceptして事前にMSWで設定していたデータをコンポーネントに戻すことができます”という意味も理解できたのではないでしょうか。

ApplicationタブのService Workersを確認すると登録されたService Workersの情報を確認することができます。

ApplicationタブのService Workersを確認
ApplicationタブのService Workersを確認

URLを相対パスにした場合

JSONPlaceHolderを利用した場合はhttps://から始まるURLを設定していましたローカルサーバ上の/api/usersにアクセスする場合にMSWでは相対パスが利用できるのか確認します。

UserList.jsxファイルのfetch関数の引数に設定するURLをhttps://…から/api/usersに変更します。


useEffect(() => {
  const getUsers = async () => {
    const response = await fetch(
      // 'https://jsonplaceholder.typicode.com/users'
      '/api/users'
    );
    const data = await response.json();
    setUsers(data);
  };
  getUsers();
}, []);

handlers.jsのrest.getの引数の値をfetch関数の引数のURLに合わせます。


import { rest } from 'msw';

export const handlers = [
  // rest.get('https://jsonplaceholder.typicode.com/users', (req, res, ctx) => {
  rest.get('/api/users', (req, res, ctx) => {
    return res(ctx.status(200), ctx.json([{ id: 1, name: 'John' }]));
  }),
];

ブラウザを利用して動作確認を行うと/api/usersの相対パスでも動作することがわかります。

Node.jsの場合

Vitestを利用してテストを行う際はNode.jsを利用します。プロジェクト作成時にテストに必要なVitest, @testing-library/react, jsdomはインストール済みなので設定を行なっていきます。

サーバの設定

VitestはNode上で実行されるためService Workerを利用することができません。そのためブラウザの場合と設定方法が一部異なりService Workerの代わりにServerの設定を行います。

src/mocksフォルダにserver.jsファイルを作成して以下のコードを記述します。


import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);

一見するとbrowser.jsファイルとの違いがわからないかもしれませんがブラウザの場合(browser.js)にmswからsetupWorkerをimportしていましたがNodeの場合(server.js)はmsw/nodeからsetupServerをimportしています。

handlers.jsファイルについてはブラウザとNodeのどちらでも利用することができます。


import { rest } from 'msw';

export const handlers = [
  rest.get('https://jsonplaceholder.typicode.com/users', (req, res, ctx) => {
    return res(ctx.status(200), ctx.json([{ id: 1, name: 'John' }]));
  }),
];

テストコード

MSWによるサーバの設定が完了したのでテストコードを作成します。componentsフォルダの中にUserList.spec.jsxファイルを作成します。


import { render, screen } from '@testing-library/react';
import { describe, it, expect, beforeAll, afterEach, afterAll } from 'vitest';
import { server } from '../mocks/server';
import UserList from './UserList';

beforeAll(() => server.listen());

afterEach(() => server.resetHandlers());

afterAll(() => server.close());

describe('UserList', () => {
  it('renders properly', async () => {
    render(<UserList />);
    const listElement = await screen.findByText(/John/);
    expect(listElement).toBeInTheDocument();
  });
});

MSWのサーバの起動はserver.listen()で開始されます。server.listen()によりrequest interceptionが行えるようになります。テストが完了したらserver.close()でrequest interceptionを停止してクリーンアップします。各テストが完了するとresetHandlersでhandlerのリセットを行っています。findByTextメソッドによって文字列Johnを含む要素が存在するかチェックを行っています。

テストはnpx vitestコマンドで実行することができます。実行すると”document is no defined”が表示されます。Node.jsではブラウザとは異なりdocumentオブジェクトを持っていないのでjsdomを利用する必要があります。


ReferenceError: document is not defined

インストールしているだけではjsdomは自動で利用されないので利用したい場合には–environmentオプションにjsdomを指定します。


 % npx vitest --environment jsdom

オプションを利用する代わりにvite.config.jsでテストの設定を行うことができるのでenvironmentとglobalsを設定します。globalsを設定することでexpect関数やit関数をテストファイルでimportする必要がなくなります。


import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
  },
});

globalsを設定した後はUserList.spec.jsxファイルからvitestからのimportを削除してください。

設定後npx vitestを実行すると”document is not defined”のエラーはなくなり、今度は別のメッセージが表示されてテストが失敗します。


Error: Invalid Chai property: toBeInTheDocument

toBeInTheDocumentを利用するためには@testing-library/jest-domが必要となるのでインストールします。


 % npm install --save-dev @testing-library/jest-dom

インストールが完了したらUserList.spec.jsxファイルで@testing-library/jest-domのimportを行います。


import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import { server } from '../mocks/server';
import UserList from './UserList';

beforeAll(() => server.listen());

afterEach(() => server.resetHandlers());

afterAll(() => server.close());

describe('UserList', () => {
  it('renders properly', async () => {
    render(<UserList />);
    const listElement = await screen.findByText(/John/);
    expect(listElement).toBeInTheDocument();
  });
});

テストを実行するとテストにパスします。ここまでの設定でNode環境でMSWを利用してテストを実行することができました。本当にMSWから戻された値が利用されているのか確認したい場合にはscreen.debug()を利用することでDOMを表示させることができます。

screen.debug()による出力は下記のような形が表示されMSWで設定した”John”が含まれていることがわかります。


stdout | src/components/UserList.spec.jsx > UserList > renders properly
<body>
  <div>
    <h1>
      ユーザ一覧
    </h1>
    <ul>
      <li>
        1
        .
        John
      </li>
    </ul>
  </div>
</body>

ReferenceError: expect is not defined

vite.config.jsを設定を行わずテストを実行した場合には”ReferenceError: expect is not defined”のエラーが表示させました。その場合は下記のように


import matchers from '@testing-library/jest-dom/matchers';

expect.extend(matchers);

UserList.spec.jsxファイルの中身は下記のようになります。


import { render, screen } from '@testing-library/react';
import { describe, it, expect, beforeAll, afterEach, afterAll } from 'vitest';
import { server } from '../mocks/server';
import UserList from './UserList';
import matchers from '@testing-library/jest-dom/matchers';

expect.extend(matchers);

beforeAll(() => server.listen());

afterEach(() => server.resetHandlers());

afterAll(() => server.close());

describe('UserList', () => {
  it('renders properly', async () => {
    render();
    const listElement = await screen.findByText(/John/);
    screen.debug();
    expect(listElement).toBeInTheDocument();
  });
});

まだMSWを利用したことがない人はぜひ利用してみてください。本文書で説明した通り簡単に設定を行うことができます。