【フロントテスト】ReactでのMSWの導入

## はじめに
はじめまして、河村康治です。HITOTSUアドベントカレンダーの４日目担当します！！
普段はバックエンドを主にやっているのですが、E2Eテストの知見を深めていきたいなと思っています。なので、今回のカレンダーではE2Eのフロントエンド周りを中心に投稿していきます！！
今回の記事では、Mockサーバを利用したフロントエンドのテストです。バックエンド開発中にフロントエンドのテストが進められるなど、何かと便利なMSWを紹介します！！

## やりたい事
通常時はAPIサーバと連動する。テスト時のみServiceWorkerが起動し固定値が返却される。
これにより、APIサーバに依存する事なく、API連動時のフロントの挙動を確認する事ができるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/3372d5b0-8027-37cd-ad1e-889bb807db05.png)

## MSWとは
ServiceWorkerを利用したMockサーバです。ネットワーク層でリクエストをインターセプトし値を返却します。そのため、アプリケーションの挙動はそのままで、テストや開発、デバッグで利用されているライブラリです。
![スクリーンショット 2022-11-06 18.24.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/0c82eef2-3cfa-3a9a-fe07-ae9fe5c79e25.png)

## Hands-onスタート

https://github.com/kouji0705/msw_test2

### 環境情報

```
## typescriptでプロジェクト作成
yarn create react-app msw_test2 --template typescript

## mswのインストール
~/develop/react$ yarn add --dev msw

## mockServiceWorke.jsをコマンドで作成
npx msw init public/ --save

## mswとは関係ないが、Hands-onで使うライブラリ
yarn add @tanstack/react-query
yarn add axios
```


```sh:ディレクトリ構成
~/develop/react/msw_test$ tree -I node_modules 
.
├── README.md
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   ├── mockServiceWorker.js
│   └── robots.txt
├── src
│   ├── App.css
│   ├── App.test.tsx
│   ├── App.tsx
│   ├── api
│   │   └── login.ts
│   ├── components
│   │   └── Login.tsx
│   ├── index.css
│   ├── index.tsx
│   ├── logo.svg
│   ├── mocks
│   │   ├── browser.ts
│   │   └── handler.ts
│   ├── react-app-env.d.ts
│   ├── reportWebVitals.ts
│   └── setupTests.ts
├── tsconfig.json
└── yarn.lock

5 directories, 24 files
```

```json:package.json
{
  "name": "msw_test",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@tanstack/react-query": "^4.14.3",
    "@testing-library/jest-dom": "^5.14.1",
    "@testing-library/react": "^13.0.0",
    "@testing-library/user-event": "^13.2.1",
    "@types/jest": "^27.0.1",
    "@types/node": "^16.7.13",
    "@types/react": "^18.0.0",
    "@types/react-dom": "^18.0.0",
    "axios": "^1.1.3",
    "msw": "^0.47.4",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "typescript": "^4.4.2",
    "web-vitals": "^2.1.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "msw": {
    "workerDirectory": "public"
  }
}
```

```:env
//　今回は、REACT_APP_ENVがtestの時のみmswが起動するように設定。
REACT_APP_ENV=test
```


### ソースコード

### mockの設定
```ts:src/mocks/browser.ts
import {setupWorker} from 'msw';
import {handlers} from './handler';

// 指定されたリクエストハンドラを持つサービスワーカーを設定する
export const worker = setupWorker(...handlers);
```

```ts:src/mocks/handler.ts
import {rest} from 'msw';

export const handlers = [
  rest.get('https://jsonplaceholder.typicode.com/todos/1', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json({
        userId: 2,
        id: 2,
        title: 'Mockですよ!!!',
        completed: false,
      })
    );
  }),
];

```


### アプリケーション側
```tsx:index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';

const queryClient = new QueryClient();

// テスト環境の時のみ、ServiceWorkerを登録する
if (process.env.REACT_APP_ENV === 'test') {
  const {worker} = require('./mocks/browser');
  worker.start();
}

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>
);

```

```tsx:src/App.tsx
import React from 'react';
import {Login} from './components/Login';

function App() {
  return <Login />;
}

export default App;
```

```tsx:src/components/Login.tsx
import {useFetchAPI} from '../api/login';

export const Login = () => {
  const {isLoading, isError, data} = useFetchAPI();

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (isError) {
    return <span>Error...</span>;
  }

  return (
    <div>
      ユーザID：{data.userId}
      <br />
      ID：{data.id}
      <br />
      タイトル:{data.title}
      <br />
      完了：{data.completed ? '完了' : '未完了'}
      <br />
    </div>
  );
};
```

```ts:src/api/login.ts
import axios from 'axios';
import {useQuery} from '@tanstack/react-query';

export const getAPI = async () => {
  const baseUrl = 'https://jsonplaceholder.typicode.com/todos/1';
  const {data} = await axios.get<User>(baseUrl);
  return data;
};

export const useFetchAPI = () => {
  return useQuery(['test'], () => getAPI());
};

export type User = {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
};
```


### 動作確認
今回は、JSONPlaceholderを利用しています。
通常はJSONPlaceholderを実行し、テスト環境の時はServiceWorkerを実行する事になります。

■通常時
![スクリーンショット 2022-11-06 18.51.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/8da0ec89-aeea-be30-8510-5600a6de4134.png)

■テスト時
![スクリーンショット 2022-11-06 18.51.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/a5e151f8-7b63-a224-724a-082f0a722625.png)

これが出ていればOKです！！！
ちなみに、MSWが起動している時は、Console画面に「[MSW] Mocking enabled.」と表示されます。

## 最後に
今回はMSWの導入について記載しました。次回移行の記事では、JestやPlayWrightでのMSW導入の話に繋げていきたいと思います！！