React✖️Material-Ui✖️Jestでclickイベントのテストをする　

# 目次
[1.はじめに](#1-はじめに)
[2.自己紹介](#2-自己紹介)
[3.開発環境](#3-開発環境)
[4.やりたいこと](#4-やりたいこと)
[5.困ったこと](#5-困ったこと)
[6.解決策](#6-解決策)
[7.おわりに](#7-おわりに)

# 1.はじめに
Material-Uiのbuttonコンポーネントのクリックイベントのテストがつまったときに参考にできるものが少なかったので記事にしました！
今回はセレクトボックを例にしています。

# 2. 自己紹介
軽く自己紹介をします
- 大学3年生
- 経済学部
- 趣味は散歩、ゲーム（FPS）、アニメ、映画

# 3. 開発環境
- React 18.2.0
- TypeScript 4.8.2
- Jest 29.0.2
- Material-Ui

# 4. やりたいこと
**やりたいこと**：セレクトボックスをクリックしてセレクトオプションをクリックして選択できることを確かめたい
![スクリーンショット 2022-12-19 22.53.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2259939/072166df-dfe6-f2cd-413b-8e26500fd82e.png)
**想定するユーザーアクション**
1. セレクトボックスをクリック
1.  「テスト2」をクリック

**想定するユーザーインターフェース**
1. セレクトボックスは「テスト1」が初期表示
1. セレクトボックスがクリックされると「テスト１」「テスト２」を表示
1. クリックされたセレクトオプションをセレクトボックスに表示


##### 構成ファイルは以下になります。
```tsx:src/App.tsx
import {useState} from "react";
import { Selection } from "./components/ui/Selection";
import { SelectChangeEvent } from "@mui/material/Select";
import "./App.css";

function App() {
  const [test, setTest] = useState("test1");
  const handleTest = (event: SelectChangeEvent) => {
    setTest(event.target.value as string)
  };
  return (
    <div className="App" style={{marginTop: 100+"px"}}>
      <Selection selectValue={test} handleChangeEntry={(event) => handleTest(event)} />
    </div>
  );
}

export default App;
```
``App.tsx``にセレクトボックスコンポーネントをimportして表示させています。
``Selection.tsx``にはクリックイベント時に値を更新する関数と``input``の``value``属性をpropsで渡してあげます。

```tsx:components/ui/Selection.tsx
import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem";
import FormControl from "@mui/material/FormControl";
import Select, { SelectChangeEvent } from "@mui/material/Select";

type Selection = {
  selectValue: string;
  handleChangeEntry: (event: SelectChangeEvent) => void;
};

export const Selection: React.FC<Selection> = (props) => {
  return (
    <>
      <FormControl sx={{ minWidth: 200 }}>
        <InputLabel>テスト１/テスト２</InputLabel>
        <Select
          data-testid="select-value"
          inputProps={{ required: true }}
          labelId="select-value"
          value={props.selectValue}
          label="selectValue"
          onChange={props.handleChangeEntry}
        >
          <MenuItem value={"test1"}>テスト１</MenuItem>
          <MenuItem value={"test2"}>テスト２</MenuItem>
        </Select>
      </FormControl>
    </>
  );
};

```
``Selection.tsx``はMaterial-Uiを使ってセレクトボックスUIを表示させています。
セレクトオプションには「テスト1」「テスト2」を置いています。

# 5. 困ったこと

よし！テストするぞ！と思い確認してみると、
うん？？うまくレンダリングされていない？？

イメージとしてはセレクトボックスを取得して、その後にセレクトオプションを取得し、選択してOKでした。
とても浅はかな考えですね（笑）

ソースコードは以下になります。

```tsx:test/ui/Selection.test.tsx
import App from "../../App";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";

describe("Selectionコンポーネント", () => {
  describe("when select correctory", () => {
    test("render correctory", async () => {
      render(<App />);
      userEvent.click(screen.getByTestId("select-value"));
      await waitFor(() => {
        fireEvent.change(screen.getByText("テスト1/テスト2"), {
          target: { value: "テスト2" },
        });
       
      });
    });
  });
});
```


 1. ``Select``を``userEvent.click``で取得
 1. 取得後、「テスト1/テスト/2」というテキストのあるタグを指定し、その中のセレクトオプション「テスト2」を選択

このようにすることでテストがうまくいくと思っていたのですが、テストで生成されたDOMを確認するすると``MenuItem``の部分がうまくレンダリングされていないことがわかりました。

# 6. 解決策

問題は``MenuItem``（セレクトオプション）の部分がレンダリングされていないこと
どうやったらレンダリングさせることができるのか？？？

その方法が以下になります。

```tsx:test/ui/Selection.test.tsx
import App from "../../App";
import { fireEvent, render, screen, within } from "@testing-library/react";
import "@testing-library/jest-dom";

describe("Selectionコンポーネント", () => {
  describe("when select correctory", () => {
    test("render correctory", async () => {
      render(<App />);
      const selectCompoEl = screen.getByTestId("select-value");
      const button = within(selectCompoEl).getByRole(
        "button"
      ) as HTMLInputElement;
      fireEvent.mouseDown(button);
      const listbox = within(screen.getByRole("presentation")).getByRole(
        "listbox"
      );
      const options = within(listbox).getAllByRole("option");
      fireEvent.click(options[1]);
      expect(button).toHaveTextContent("テスト２");
    });
  });
});
   ...
```
一つずつ説明していきます。
1. ``selectCompoEl``にMUIの``Select``部分を代入
   ```tsx
   const selectCompoEl = screen.getByTestId("select-value");
   ```

2. セレクトオプションを展開させるためにクリックイベントを発火させたいのでMUIの``Select``の子要素にある ``button``ロールを持つ要素を取得し、``button``に代入
   ```tsx
   const button = within(selectCompoEl).getByRole("button") as HTMLInputElement;
   ```
3. 上記で取得した要素を``mouseDown``する
   ```tsx
   fireEvent.mouseDown(button);
   ```
4. セレクトオプション部分がレンダリングされるのでリストを囲っている部分を取得したいので``presentation``ロールを親に持つ``listbox``ロールを持つ要素を取得し、``listbox``に代入
   ```tsx
   const listbox = within(screen.getByRole("presentation")).getByRole("listbox");
   ```
5. セレクトオプションを全て取得したいので``listbox``ロールを親に持つ``option``ロールを持つ要素を取得し、``options``に代入
   ```tsx
   const options = within(listbox).getAllByRole("option");
   ```
6. 今回は「テスト2」を選択するので``options``配列の２つ目の要素に対してクリックイベント
   ```tsx
   fireEvent.click(options[1]);
   ```
7. セレクトボックスの値が「テスト2」と一致すればOK
   ```tsx
   expect(button).toHaveTextContent("テスト２");
   ```

# 7. おわりに
今回はReact✖️Material-Ui✖️Jestでclickイベントのテストをしているところを切り出してみました！
どなたかの役に立つものであると嬉しいです！

今後も機会があれば技術系の記事を書いてみようと思います。

### 参考にしたもの
https://github.com/testing-library/react-testing-library/issues/322