5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Do'erAdvent Calendar 2022

Day 16

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

Last updated at Posted at 2022-12-18

目次

1.はじめに
2.自己紹介
3.開発環境
4.やりたいこと
5.困ったこと
6.解決策
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
想定するユーザーアクション

  1. セレクトボックスをクリック
  2. 「テスト2」をクリック

想定するユーザーインターフェース

  1. セレクトボックスは「テスト1」が初期表示
  2. セレクトボックスがクリックされると「テスト1」「テスト2」を表示
  3. クリックされたセレクトオプションをセレクトボックスに表示
構成ファイルは以下になります。
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にはクリックイベント時に値を更新する関数とinputvalue属性をpropsで渡してあげます。

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>テスト1/テスト2</InputLabel>
        <Select
          data-testid="select-value"
          inputProps={{ required: true }}
          labelId="select-value"
          value={props.selectValue}
          label="selectValue"
          onChange={props.handleChangeEntry}
        >
          <MenuItem value={"test1"}>テスト1</MenuItem>
          <MenuItem value={"test2"}>テスト2</MenuItem>
        </Select>
      </FormControl>
    </>
  );
};

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

5. 困ったこと

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

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

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

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. SelectuserEvent.clickで取得
  2. 取得後、「テスト1/テスト/2」というテキストのあるタグを指定し、その中のセレクトオプション「テスト2」を選択

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

6. 解決策

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

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

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("テスト2");
    });
  });
});
   ...

一つずつ説明していきます。

  1. selectCompoElにMUIのSelect部分を代入

    const selectCompoEl = screen.getByTestId("select-value");
    
  2. セレクトオプションを展開させるためにクリックイベントを発火させたいのでMUIのSelectの子要素にある buttonロールを持つ要素を取得し、buttonに代入

    const button = within(selectCompoEl).getByRole("button") as HTMLInputElement;
    
  3. 上記で取得した要素をmouseDownする

    fireEvent.mouseDown(button);
    
  4. セレクトオプション部分がレンダリングされるのでリストを囲っている部分を取得したいのでpresentationロールを親に持つlistboxロールを持つ要素を取得し、listboxに代入

    const listbox = within(screen.getByRole("presentation")).getByRole("listbox");
    
  5. セレクトオプションを全て取得したいのでlistboxロールを親に持つoptionロールを持つ要素を取得し、optionsに代入

    const options = within(listbox).getAllByRole("option");
    
  6. 今回は「テスト2」を選択するのでoptions配列の2つ目の要素に対してクリックイベント

    fireEvent.click(options[1]);
    
  7. セレクトボックスの値が「テスト2」と一致すればOK

    expect(button).toHaveTextContent("テスト2");
    

7. おわりに

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

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

参考にしたもの

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?