フロントエンドで自動ユニットテストについて

# はじめに
TRIAL＆RetailAI Advent Calendar 2024 の 8日目の記事です。

昨日は @zushi_ryotaさんの『[D言語でAtCoderを解きたい！](https://qiita.com/zushi_ryota/items/46d0aabbd8fb323915b7)』という記事でした。
D言語見ると、学生時代勉強してたC言語の思いも出ました。少し似てるような感じです。
> プロ棋士の佐藤天彦九段の「居飛車は仕事、振り飛車は恋愛」に倣うと、「Java・Kotlinは仕事、Dは恋愛」とでも言ったところでしょうか。

D言語だけでなくC言語に興味がある方は、ぜひ『[D言語でAtCoderを解きたい！](https://qiita.com/zushi_ryota/items/46d0aabbd8fb323915b7)』見て見てください！！！



# 背景
最近のプロジェクトでは、スクラム開発手法が広く採用されています。スクラムでは、ユーザーのニーズに応じて迅速に開発・リリースを行うため、開発期間が短縮されます。しかしその一方で、既存の機能改善や新機能追加が頻繁に行われるため、テスト範囲が急激に拡大しています。これにより、品質に関して高い水準を維持する必要があり、QAチームの負荷が増大し、プロジェクト全体の品質に対する懸念が増しています。

# フロントエンド側で自動ユニットテストを導入する必要性
- テスト範囲の拡大に対応するための効率化
    - 自動ユニットテストを導入することで、リグレッションテストを効率的に実行でき、スクラムのように頻繁なリリースサイクルでも品質を維持できる
- 品質の向上
    - ユニットテストで、機能単位のコンポーネントや関数で正確性を確認できるので、不具合も早期発見が可能です
- QAチームの負担軽減できて、スピードも上がれる
    - 機能単位のテストを自動化できて、テスト全体の効率が向上します
- フロントエンド開発者は自分たち開発したものにもっと自信がある
    - ユニットテストを準備することで、開発者がすぐに書いているコードの品質や影響範囲を把握でき、迅速に修正が可能になります( [The more your tests resemble the way your software is used, the more confidence they can give you.](https://testing-library.com/docs/guiding-principles/))

# React Testing Library
Reactコンポーネントのテストのために、今回選んで学習するのは軽量なライブラリであるReact Testing Libraryです。
### テストケースのベース構成
![スクリーンショット 2024-12-06 22.23.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2987895/ef8614f2-fb5e-19df-11c7-dfa290d9f317.png)
- 書き方は２パターン
    - `test('テストブロックを命名する', () => {})`
    - `it('テストブロックを命名する', () => {})`
- 例
``` typescript
// パターン１
test('テキスト入力コンポーネントで入力して表示されること', async () => {
    // テストしたいコンポーネントをrenderする
    render(<InputComponent />)
    
    // 操作したい要素を見つける
    const inputElement = screen.getByPlaceholderText(/テキスを入力ください/i)

    // それらの要素と操作を行う
    fireEvent.change(inputElement, { target: { value: '新しいテキスト' } })

    // 結果が期待通りであることを確認する
    expect(inputElement.value).toBe('新しいテキスト')
  })

// パターン２
it('テキスト入力コンポーネントで入力して表示されること', async () => {
    // テストしたいコンポーネントをrenderする
    render(<InputComponent />)
    
    // 操作したい要素を見つける
    const inputElement = screen.getByPlaceholderText(/テキスを入力ください/i)

    // それらの要素と操作を行う
    fireEvent.change(inputElement, { target: { value: '新しいテキスト' } })

    // 結果が期待通りであることを確認する
    expect(inputElement.value).toBe('新しいテキスト')
  })
```
- テストしたいコンポーネントをrenderする（`testing-library/react`の`render`）
    - コンポーネント内で`react-router-dom`のものが使われている場合、テストでコンポーネントへrenderする時も同じく、`react-router-dom`の`<BrowserRouter/>`より囲まれる
    - コンポーネントのパラメーターで関数がある場合、`jest.fn()`よりモックファンクションを作成して渡す

```typescript
 // テストしたいコンポーネント
import React from "react";
import { Link } from "react-router-dom";

export const MyLink = () => {
  return (
    <div>
      <h2>This is my link page</h2>
      <Link to="/OtherPage">Move to OtherPage </Link>
    </div>
  );
};

// テストで書く時
import MyLink from "../MyLink";
import { BrowserRouter } from "react-router-dom";

const MockMyLink = () => {
  return (
    <BrowserRouter>
      <MyLink />
    </BrowserRouter>
  );
};

it('コンポーネントでreact-router-domの物が使われている', async () => {
    // テストしたいコンポーネントをrenderする
    render(<MockMyLink />)
}
```

- 操作したい要素を見つける（`testing-library/react`の`screen.`）
    - `.getBy`
    - `.findBy`
    - `.queryBy`
     - `.getAllBy`
    - `.findAllBy`
    - `.queryAllBy`
    - ...

※各ファンクションより返してくれるもの
![スクリーンショット 2024-12-06 23.58.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2987895/99bf2615-24d3-086f-beb1-507f14be7ae4.png)

- それらの要素と操作を行う（`testing-library/react`の`fireEvent.`）
    - 必要に応じてイベントを選べる
![スクリーンショット 2024-12-07 0.11.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2987895/87103154-4517-d334-6163-bba9d3f6a54d.png)

- 結果が期待通りであることを確認する（`expect().`）
    - `.toBe`
    - `.toContain`
    - `.toEqual`
     - `.toHave`
    - `.toMatch`
    - `.not.toBe`
    - `.not.toContain`
    - `.not.toEqual`
     - `.not.toHave`
    - `.not.toMatch`
    - ...

# 最後
現在はまだユニットテストの基礎知識を初歩的に学んでいる段階です。今後も継続して学びを深め、実際のプロジェクトで活用した感想を含めて、また記事でまとめていきたいですと思っています。

## FYI
明日は、@fujihara_hideyukiさんが、「flutter_genでアセット管理してみた」を使ってテストコードを書いてみる！ぜひ、お楽しみください〜