フロントエンド 自動テスト 始めました

## フロントエンドで自動テスト始めました

僕は自動テストのないPJで苦しんだ経験から、自分が担当するPJではテストを必ず書くようにしました。

というのも最近の数ヶ月ことなので、テストを書く上で気をつけていることをざっとメモっていこうと思います。

## 環境
### フレームワーク
React 18系
NextJS 14系
### テストライブラリ
JEST 29系
TestingLibrary 16系

### 静的解析
TypeScript 5系
ESLint 8系

## どうしてテストがないときついか?
そもそもテストを書くモチベがないとテスト書かないですよね...?

僕が「テストが必要」だと感じている理由は主に３つあって
- **デグレの防止**
- **リファクタリングを容易にする**
- **テスト容易性なコードを書く力学が働くようになる**

だと思っています。


### デグレの防止
これはなんとなく想像がつくと思います。
開発につきものな「ライブラリの乗り換え」「機能追加」などのために、
既存コードを変更した時に、うっかり壊しちゃうことがあります。

テストがあるとこういった異常を検知しやすくなります。


### リファクタリングを容易にする
ある意味、TDD(テスト駆動開発)と似たような考え方かもしれません。

「正しく動作していなければプロダクトとしての価値はない」という考えのもと
- その価値を失わないように変更する
- 一時的に価値が失われる瞬間があっても、その時間は最小限にとどめる

を意識しています。
「リファクタしたと思っていたけど、別の箇所で壊れていた」
という事態を避け、**プロダクトの価値を保持したまま安心してリファクタリングを進めたいです**。

そのためにも**既存コードが壊れていないことを担保し続ける**テストが必要です。

### テスト容易性なコードを書く力学が働くようになる
僕はこのモチベが一番大きいです。

**テストを書くと、良い設計になっているか検証できます** 。

**悪い設計の特徴として、テスタビリティの低さ** があると思っています。

「なんかテストしにくいなー」→「設計がどっか悪いんかな？」
と考えるようにしています。

- 依存関係が多すぎて、テストしたいことと関係ないライブラリのモックが多いなー
- 特定の`function`だけテストしたいのに、別の`function`が介在しているせいで、考慮しないといけないことが多いなー
- たくさん`props`渡さないとテストが実行できないなー

みたいに **「嫌な感じ」** がすると、テストを楽にできないか実装を見直すことが多いです。
（大体設計が悪いです）



## テスティングライブラリ
### 単体テスト
個々の関数やコンポーネントが期待通り動作するかテストします。
私は[Jest](https://jestjs.io/ja/)を使用しています。

フロントエンドの単体テスト・結合テストでは`Jest`1強です。

ここ最近で、`Jest`互換である[Vitest](https://vitest.dev/)が登場しておりますが、
担当する`PJ`の規模を鑑みてコミュニティが大きい`Jest`を採用しています。

### 結合テスト
各コンポーネントや関数を組み合わせた時に、期待通りに動作するかをテストします。
私は[Jest](https://jestjs.io/ja/)と[React Testing Library](https://testing-library.com/)  を使用しています。

### E2Eテスト
こちらはまだ追加していません。


## 実際にどんな感じで書くの？
実行手順は基本的に３つ
- コンポーネントを`rendering`して
- 要素を取得/なんらかの操作して
- 結果を検証する

いわゆる `AAA`パターンというテスト手法を取り入れています。
[こちらの記事](https://zenn.dev/y_hsgw/articles/3ad9287863cc1a) で勉強させていただきました。

これもテストの書き方は人それぞれなので、放置するといろんな書き方のバリエーションが生まれます。

書き方は統一した方が些細なことに悩まないで済みます。


## テストは英語で書く
これ、**意外と良い...** と思いました。

- 自然言語っぽく書ける
- 書き方が統一される
-  テストのしにくさ・テストの複雑さに気づく時がある

### 自然言語っぽく書ける
`it` か `test` どっちでもいいのですが僕は `it` にしています。
例えば、コンポーネントがレンダリングされていることをテストしたい時
```jsx
  it('renders as default', () => {
    render(<Button />);

    expect(screen.getByRole('button')).toBeVisible();
  });
```
`it` を主語にして`It renders as default.`
超シンプルだし、自然に読めるので個人的に好みです。

### 書き方が統一される
これもコード規約を作れば済む話なのですが、一応。
流暢に英語を書けない限り、書き方のバリエーションはある程度統一してきます。

日本語だとテストケースなど好き勝手に書けちゃうので...
さっきの例だと
- 「コンポーネントをレンダリングできることを確認」
- 「コンポーネントを描画できる」
- 「ボタンコンポーネントを表示できる」
とか、同じ意味でも結構違う文言で書かれてしまいます。

### テストのしにくさ・テストの複雑さに気づく時がある
これは僕限定かもしれませんが、英語だとテストケースを書いていて
**「ケースを表現しにくいな〜」と思うことがあります。**

(テストにいっぱい準備が必要な時とか...)

例えば以下の日本語だと英語にするの結構めんどくさいと思うんですよね。
（副文いっぱいだし）
```
「売上」ボタンをクリックして「商品」ボタンにカーソルを当てると「売上」ボタンの選択状態が解除され、「商品」ボタンが選択状態になる
```

なので、シンプルなテストケースになるように
テスト自体をシンプルにできないかいつも考えています。


↓ これくらい簡単なケース名でないときついっす
```
メニューをクリックすると選択状態になる
=> It becomes selected when clicking the menu.

選択状態はホバーで移動する
=> It moves the selected state on hover.
```

## シンプルに書く
コードだけでなく、テストもシンプルに短くしていきたいです。

### 変数を活用して、「似たようなテスト」はまとめる

例えば、「disabled なボタンはクリックイベントが発火しない」ことをテストしたい時...

```jsx
  it.each([
    { disabled: undefined, calledTimes: 1 },
    { disabled: false, calledTimes: 1 },
    { disabled: true, calledTimes: 0 },
  ])(
    'calls onClick $calledTimes time when disabled is $disabled',
    ({ disabled, calledTimes }) => {
      const onClick = jest.fn();
      render(<Button disabled={disabled} onClick={onClick} />);

      screen.getByRole('button').click();

      expect(onClick).toHaveBeenCalledTimes(calledTimes);
    },
  );
```

これを実行すると以下のような結果になります。
```
    ✓ calls onClick 1 time when disabled is undefined (9 ms)
    ✓ calls onClick 1 time when disabled is false (6 ms)
    ✓ calls onClick 0 time when disabled is true (3 ms)
```

このように`it.each`を使用して、テストを短くまとめることができます。

参考: [test.each(table)(name, fn, timeout)](https://jestjs.io/ja/docs/api#testeachtablename-fn-timeout)


そのほか[%s](https://jestjs.io/ja/docs/api#1-testeachtablename-fn-timeout)などを活用してユニークなテストが書けます。
```tsx
  it.each([undefined, []])('does not render when items is %s', (items) => {
    const { container } = render(<ListItem items={items} />);

    expect(container).toBeEmptyDOMElement();
  });
```

こうした工夫を入れることでテストファイルがかなりスッキリします。

## 超基本的なデバッグ方法

TestingLibraryが[便利な方法](https://testing-library.com/docs/dom-testing-library/api-debugging/#screendebug)を用意してくれています。


いつも重宝するのは [screen.debug()](https://testing-library.com/docs/dom-testing-library/api-debugging/#screendebug) で、
レンダリングされたDOM要素を表示して特定の要素が意図通りに表示されるか確認できます。


## ユーザーの操作に沿ったテストにすること

`TestingLibrary` を例にします。

### やりがちなテスト方法
`document.getElemetById`、 `document.querySelector` を使用して
クラス名やIDを使用して要素を取得する。
```tsx
  it('renders circle class when variant props is circle', () => {
    const { container } = render(<Image variant="circle" />);

    expect(container.querySelector('img')).toHaveClass('circle');
  });
```
これでもテストは通るんですけど**人間が実際のアプリケーションを操作する時に、IDやクラス名を確認しながらテストを書かないですよね？**

### Priorityを意識してテストを書く
[Priority](https://testing-library.com/docs/queries/about/#priority)にある通り、`Role`属性・ラベル・テキスト・入力値のようなマシーン/ヒューマンリーダブルな要素を取得してテストを実行します。

```tsx
  it('renders circle class when variant props is circle', () => {
    render(<Image alt="testImage" variant="circle" />);

    //　余談:  img role は alt がないと取得できない
    expect(screen.getByRole('img')).toHaveClass('circle');
  });
```
**できる限り、`role`属性を指定して要素を取得する方がベターです。**
アクセシビリティロールが実装段階で設置できているかの確認にもなります。

どのようなRoleがあるかは[WAI-ARIA 1.3 日本語訳](https://momdo.github.io/wai-aria-1.3/#role_definitions)が容易されているのでそちらを参考にしたいです。

## テストしておきたい項目
基本的にロジックは`utils`, `hooks`に分離し、
**`uiコンポーネント`の責務はできるだけ、レンダリングのみ**に集中したいです。

その上で、コンポーネントのテストでは以下の項目をテストします。
- コンポーネントがレンダリングされる
- `prop`が渡されている
    - レンダリングされているならその存在チェック
    - `function` なら呼び出されていることをチェック

実際に簡単なボタンコンポーネントで例を挙げてみます。

### ボタンコンポーネント
```tsx
import React, { ReactNode } from 'react';

type ButtonProps = {
  type?: 'button' | 'submit';
  onClick?: () => void;
  disabled?: boolean;
  className?: string;
  children?: ReactNode;
};

export const Button = ({
  type = 'button',
  onClick,
  disabled = false,
  className = '',
  children,
}: ButtonProps) => {
  return (
    <button
      type={type}
      onClick={onClick}
      disabled={disabled}
      className={className}
    >
      {children}
    </button>
  );
};

```

「Propsには`ButtonHTMLAttributes<HTMLButtonElement>`を使おうよ...」
とかツッコミあるかもしれませんが、できるだけ簡単なボタンを意識して書きました。


### コンポーネントがレンダリングされる

```tsx
  it('renders as default', () => {
    render(<Button />);

    expect(screen.getByRole('button')).toBeVisible();
  });
```
単純にコンポーネントが描画されることをチェックします。

tipsとして、要素があるかどうかを確認する時は、マッチャーに`toBeInTheDocument`ではなく[toBeVisible](https://github.com/testing-library/jest-dom?tab=readme-ov-file#tobevisible)を使うことです。

`toBeVisible` は、以下のように単に描画されるだけでなく、ヒューマン/マシーンが視認できるかどうかも確認してくれます。
>it is present in the document
it does not have its css property display set to none
it does not have its css property visibility set to either hidden or collapse
...

### `prop`が渡されている

propとして渡した要素がレンダリングされることを確認します。
```tsx  
  it('renders children', () => {
    render(<Button>ボタン</Button>);

    expect(screen.getByText('ボタン')).toBeVisible();
  });
```

実際は正常ケースの`props`を用意して、まとめてアサートすることが多いです。
ただ、`onClick` のテストはレンダリングテストの `AAA` の `Action` が異なるので分けてテストします

```tsx  
  const props = {
    children: 'ボタン',
    type: 'submit',
    disabled: true,
    className: 'text-red',
    onClick: jest.fn(),
  } as ButtonProps;

  it('renders props', () => {
    render(<Button {...props} />);

    expect(screen.getByText('ボタン')).toBeVisible();
    expect(screen.getByRole('button')).toHaveClass('text-red');
    expect(screen.getByRole('button')).toHaveAttribute('type', 'submit');
    expect(screen.getByRole('button')).toBeDisabled();
  });
```

こうしておくとテストの時にどんなプロパティについてテストするのかが見やすいです。
↓だと`className`のテストをしたいことが一目瞭然です。
```tsx
  it('applies custom className', () => {
    render(<Button {...props} className="custom-class" />)
  });
```

## クエリチートシート
[公式](https://testing-library.com/docs/react-testing-library/cheatsheet/#queries)より拝借しています。
（あまりに使ったことがないものは非掲載です）

### メソッドのサフィックスについて
`TestingLibrary`の`getBy*`、`queryBy*`、および`findBy*`メソッドには、異なる用途とシナリオがあります。

- `getBy*`: 指定した条件に一致する要素が**必ず**存在する場合に使用
- `getAllBy*`: 指定した条件に一致する**複数の**要素を取得する時に使用
- `queryBy*`: 条件に一致する要素が**存在しない可能性がある**場合に使用
- `queryAllBy*`: 条件に一致する複数の要素を取得しますが、見つからなければ空配列を返却する
- `findBy*`: **非同期で**要素を取得する場合に使用
- `findAllBy*`: **非同期で**条件に一致する**複数の**要素を取得する時に使用

### ByRole
`ariaロール`を持つ要素を検索
アクセシビリティ要件に準拠したテストを行う際に特に有用
**まずこのクエリを使うことを検討すべし**

```jsx
<button role="button">Click me</button>
// getByRole("button")でこのbuttonを取得可能
```

### ByText 
要素の中のテキスト内容（特に静的なテキスト）が特定の文字列と一致する要素を検索
```tsx
<button>Submit</button>
// getByText("Submit")でこのbuttonを取得可能
```

### ByLabelText
 `<label>タグ`と関連付けられた要素、または`aria-label属性`を持つ要素を検索
 
```tsx
<label for="username">Username</label><input id="username" />
// メソッド: getByLabelText("Username")でこのinputを取得可能
```

### ByPlaceholderText
`placeholder属性`を持つ`<input>`や`<textarea>`などの入力要素を検索
```tsx
<input placeholder="Enter username" />
// getByPlaceholderText("Enter username")でこのinputを取得可能
```

### ByAltText
画像やメディア要素の`alt属性`を使用して検索
``` tsx
<img alt="Profile picture" src="profile.jpg" />
// getByAltText("Profile picture")でこのimgを取得可能
```

### ByDisplayValue
`<input>`や`<textarea>`の現在の値で要素を検索
選択リストの選択中の値にも適用可能

```tsx
<input value="test value" />
// getByDisplayValue("test value")でこのinputを取得可能
```

### ByTitle
任意の要素の`title属性`、またはSVG要素内の`<title>タグ`で検索
```tsx
<div title="tooltip">Hover here</div>
// getByTitle("tooltip")でこのdivを取得可能
```

### ByTestId
テスト専用のカスタム属性（`data-testid`: 開発者がテスト用に付与する属性）で要素を検索

```tsx
<div data-testid="custom-element">Test Element</div>
// getByTestId("custom-element")でこのdivを取得可能
```

## JestDom チートシート
[公式の内容](https://github.com/testing-library/jest-dom?tab=readme-ov-file#tobevisible)を拝借しています。
かなり多いので個人的に使いそうなものにしています。

### toBeDisabled / toBeEnabled
要素が無効か有効かをチェック
```tsx
expect(screen.getByRole('button')).toBeDisabled();
expect(screen.getByRole('textbox')).toBeEnabled();
```

### toBeEmptyDOMElement
 要素が空であるか（子要素やテキストがないか）をチェック
 
```tsx
//  propsがないと何も描画しないコンポーネント
const { container } = render(<Badge />);

expect(container).toBeEmptyDOMElement();
```


### toBeInTheDocument
要素がDOMに存在するかどうかをチェック
基本的に`toBeVisible`を優先する

```tsx
expect(screen.getByRole('button')).toBeInTheDocument();
```

### toBeVisible
要素が画面に見えているかをチェック

```tsx
expect(screen.getByText('Loading...')).toBeVisible();
```

### toHaveClass
要素に特定のCSSクラスが適用されているかをチェック

```tsx
expect(screen.getByTestId('alert')).toHaveClass('warning');
```

### toHaveAttribute
要素が特定の属性を持っているかをチェック
```tsx
expect(screen.getByRole('button')).toHaveAttribute('type', 'submit');

expect(screen.getByRole('link')).toHaveAttribute('href', 'https://example.com');
```

### toHaveFocus
要素にフォーカスがあるかをチェック

```tsx
expect(screen.getByLabelText('Username')).toHaveFocus();
```

### toBeInvalid / toBeValid
フォームの入力フィールドが無効か有効かをチェック
バリデーションのテストで使用

```tsx
expect(screen.getByLabelText('Email')).toBeInvalid();
expect(screen.getByLabelText('Email')).toBeValid();
```

### toBeRequired
入力要素が必須（required属性）かどうかをチェック
```tsx
expect(screen.getByLabelText('Name')).toBeRequired();
```

### toHaveFormValues
フォームが特定の値を持っているかを一括でチェック

```tsx
expect(screen.getByRole('form')).toHaveFormValues({ username: 'JohnDoe', email: 'john@example.com' });
```

### toHaveStyle
要素に特定のインラインスタイルが適用されているかをチェック

```tsx
expect(screen.getByTestId('container')).toHaveStyle('display: flex');
```

### toHaveTextContent
要素に特定のテキストが含まれているかをチェック

```tsx
expect(screen.getByTestId('message')).toHaveTextContent('Hello, World!');
```

### toBeChecked / toBePartiallyChecked
チェックボックスやラジオボタンが選択されているかをチェック
`toBePartiallyChecked`は一部選択された状態がある場合で使用する
```tsx
expect(screen.getByLabelText('Agree to terms')).toBeChecked();
```


## Jest マッチャー チートシート
[リファレンス](https://jestjs.io/ja/docs/expect#%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9)から拝借しています
これはかなり多いので使っているものだけにします

## 値が同等かどうかをテストする
### toBe(value)
値が特定のプリミティブ型に一致するかをチェック
```tsx
expect(result).toBe(10);
```

### toEqual(value)

値が同等であるかをチェック
オブジェクトや配列の内容を比較する
```
expect(obj).toEqual({ key: 'value' });
```

### toStrictEqual
オブジェクトが同じ構造と型を持つかどうかをチェック
クラスや、配列内の未定義(undefined)なプロパティを含む、厳密なオブジェクトの比較に使用する
（大体 toEqualで十分なケースが多い気がする）

### toMatch(regexp | string)

文字列が正規表現や特定の文字列と一致するかをチェック
```tsx
expect('Hello World').toMatch(/World/);
```

### toBeNull()
値がnullであるかをチェック
```tsx
expect(value).toBeNull();
```

### toBeTruthy() / toBeFalsy()
値がtrue/falseであるかを確認する際に使用します。
```tsx
expect(value).toBeTruthy();
```

### toBeUndefined() / toBeDefined()
undefined であることをチェック
単にundefinedではないことを確認するなら、toBeDefined でアサーションするといい


## モック関数の呼び出しをテストする

### toHaveBeenCalled()
モック関数が呼び出されたことをチェック
```tsx
expect(mockFn).toHaveBeenCalled();
```

### toHaveBeenCalledTimes(number)
モック関数が指定回数だけ呼ばれたかをチェック
```tsx
expect(mockFn).toHaveBeenCalledTimes(2);
```

### toHaveBeenCalledWith(arg1, arg2, ...)

モック関数が指定した引数で呼ばれたかをチェック
```tsx
expect(mockFn).toHaveBeenCalledWith('value1', 'value2');
```


## 配列・オブジェクトに関するマッチャー

### toHaveLength(number)
配列や文字列が特定の長さを持つかをチェック
```tsx
expect(arr).toHaveLength(3);
```

### toContain(item)
配列や文字列が特定のアイテムを含んでいるかをチェック 
主に、プリミティブ値や参照の一致に使用する
```tsx
expect(arr).toContain('item');
```

### toContainEqual(item)
配列が特定の値と同等のアイテムを含んでいるかをチェック
`toContain`と違って、オブジェクトやネストした配列の一致に使用する

オブジェクトの比較
```tsx
const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
];

expect(users).toContainEqual({ id: 1, name: 'Alice' });
```

配列の比較
```tsx
const matrix = [
  [1, 2],
  [3, 4],
  [5, 6],
];

expect(matrix).toContainEqual([3, 4]);
```

## 数値の大小を比較する
### toBeGreaterThan(number) / toBeLessThan(number)

値が指定の値より大きいか/小さいかをチェック
```tsx
expect(result).toBeGreaterThan(10);
expect(result).toBeLessThan(10);
```

### toBeGreaterThanOrEqual(number) / toBeLessThanOrEqual(number)
値が指定の値以上であるかをチェック
```tsx
expect(result).toBeGreaterThanOrEqual(10);
expect(result).toBeLessThanOrEqual(10);
```


## イベントを実行する
「クリックするとfunctionが呼び出される〜」というようにテスト中でイベントを発火させたい時は、[fireEvent](https://testing-library.com/docs/dom-testing-library/api-events/)を使用します。

```tsx
// ボタンをクリックする
fireEvent.click(screen.getByRole('button'))
```

DOMイベントを発火させるには[userEvent](https://github.com/testing-library/user-event)もあります。

>fireEvent dispatches DOM events, whereas user-event simulates full interactions, which may fire multiple events and do additional checks along the way.

[公式の見解](https://testing-library.com/docs/dom-testing-library/api-events/) 的には、`userEvent` ではユーザーの挙動をよりリアルに再現していることから、公式でもこっちを使うことが推奨されています。

`userEvent`と`fireEvent`の違いについては、[公式のドキュメント](https://testing-library.com/docs/guide-events/)に明確な説明が記載されています。

## 非同期処理をテストする

### [waitFor](https://testing-library.com/docs/dom-testing-library/api-async/#waitfor)

何らかの時間を待つ必要がある場合は、waitForを使用して、期待が過ぎるのを待つことができます。

↓では非同期で実行されているフォームのサブミット処理をテストしています。
```tsx
  it('calls onSubmit when clicking submit button', async () => {
    render(<Form />);

    fireEvent.click(screen.getByRole('button', { name: 'form.key.submit' }));

    await waitFor(() => expect(onSubmit).toHaveBeenCalledWith(calledWith));
  });
```
非同期処理を実行するので、テスト関数に`async`をつけ、`waitFor`に`await`を付与します。

そうしないと、非同期の処理が終わる前にアサーションが実行されて意図通りの結果が得られないことがあります


### `findBy*` のメソッドは 内部的にwaitFor を実行している
単に非同期処理を待って要素を取得するのであれば`findBy`でも同じような書き方ができます。
[公式ドキュメント](https://testing-library.com/docs/dom-testing-library/api-async/#findby-queries)でも記載があります。

ここは好みの問題になると思います。

## 時間を操作する
今の時間を特定の時間に固定したい時は[useFakeTimers](https://jestjs.io/ja/docs/jest-object#jestusefaketimersfaketimersconfig)を使います

### 任意の時間を経過したことにする
```tsx
jest.useFakeTimers();
jest.advanceTimersByTime(number);
```

### 現在時刻をコントロールする
偽のタイマーで使用される現在システム時刻を変更するには[setSystemTime](https://jestjs.io/ja/docs/jest-object#jestsetsystemtimenow-number--date)を使用します。

```tsx
  beforeEach(() => {
    jest.useFakeTimers().setSystemTime(new Date('2024-10-21T09:00:00.00+09:00'));
  });

  afterEach(() => {
    jest.useRealTimers();
  });
```

`jest.useFakeTimers()` を呼び出すと、ファイル内のすべてのテストで偽のタイマーが使用されるので、[jest.useRealTimers()](https://jestjs.io/ja/docs/jest-object#jestuserealtimers) で元のタイマーを復元させる必要があります。

## 関数をモックする

コンポーネント内で関数を実行している場合、その関数の返り値を固定したい時があります。
そんな時は[jest.mock](https://jestjs.io/ja/docs/jest-object#jestmockmodulename-factory-options)を使用します。
（**既存のモジュールの全てを上書き**してモック化したい時に使用するイメージです）

例えば以下のように`button` コンポーネント内で`getButtonText`という`util`を実行しているとする。
```tsx
  import getButtonText from '@/utils/button.util';

  export const Button = (props: ButtonProps) => {
  const buttonText = getButtonText(props.text)
  ...
```

テストではこういった関数の返り値を固定したい時があります。
以下のように引数をそのまま返却するようにコントロールできます。
```tsx
jest.mock('@/utils/button.util', () => ({
  getButtonText: (data: string) => data,
}));
```

## 関数の振る舞いをモックする
コールバック関数のテストや特定の関数がどのように呼び出されるべきかをテストするときに[jest.fn()](https://jestjs.io/ja/docs/mock-function-api)が便利です。

`jest.fn()` の[mockFn.mockReturnValue(value)](https://jestjs.io/ja/docs/mock-function-api#mockfnmockreturnvaluevalue)を使用します。


例えば以下のようなhookのテストを書くことを考えてみる
このカスタムフック `useSearchParams` は、`next/navigation` の `useSearchParams` フックをラップしたものです。
```tsx
import { useSearchParams as useNextSearchParams } from 'next/navigation';

export function useSearchParams(param: string) {
  const searchParams = useNextSearchParams();

  const urlParam = parseInt(`${searchParams.get(param)}`);

  return { [param]: isNaN(urlParam) ? undefined : urlParam };
}
```

ではテストをどう書いていくか...
まずは`useNextSearchParams()`部分をモック関数に置き換えてみる。
```tsx
import { useSearchParams as _useSearchParams } from 'next/navigation';

jest.mock('next/navigation', () => ({
  useSearchParams: jest.fn(),
}));
const useNextSearchParams = _useSearchParams as jest.Mock;
```

以下では `next/navigation モジュール`をモックし、そのモジュール内の `useSearchParams` 関数を `jest.fn(`) に置き換えている。
```tsx
jest.mock('next/navigation', () => ({
  useSearchParams: jest.fn(),
}));
```

以下では、`_useSearchParams` を `jest.Mock 型`にキャストすることで、モック関数として扱えるようにする。
（これにより、`useNextSearchParams`関数が呼び出された際の引数や結果を保持できるようになる）

ここで`useSearchParams` が返す値を`mockReturnValue(...)` などを用いて、任意の検索パラメータを返すように設定できる。
```tsx
const useNextSearchParams = _useSearchParams as jest.Mock;
```


実行部分は以下のようになる。
`mockReturnValue` を使用することで [URLSearchParams](https://developer.mozilla.org/ja/docs/Web/API/URLSearchParams)オブジェクトを返却している。
```tsx
    it('returns param infomation from search params', () => {
      useNextSearchParams.mockReturnValue(new URLSearchParams('?postId=1'));

      const { result } = renderHook(() => useSearchParams('postId'));

      expect(result.current).toStrictEqual({ postId: 1 });
    });
```


また、モックオブジェクトはモックした`function`が呼び出された際の引数や結果を保持しているので、以下のようにどんな引数で、何回実行されたかをアサーションすることもできる。
```tsx
expect(mockFn).toHaveBeenCalledTimes(2);
```




## 手動テストは必要か？
コンポーネントテストを充実させても、**手動テストは必要です**

どうしても細かいデザインのテストを網羅したテストを書こうとするとキリが無いので、**僕は「レンダリング」「Props」のテストのみと決めています。**

そのため、他のコンポーネントと組み合わさってできたデザインと細かいデザインの確認は`storybook`を使用して確認しています。


## 最後に
ここまで見て頂きありがとうございます。m(._.)m

社内でフロントエンドのテストを触ってもらう前に「これ読んどいて〜」と言いたいために書きました。

なので、いいテストの書き方や知見が見つかれば随時アップデートの予定です！

## 参考
[Jest × Testing Libraryを用いた単体テストの考え方/使い方](https://zenn.dev/y_hsgw/articles/3ad9287863cc1a)

[Queries](https://testing-library.com/docs/react-testing-library/cheatsheet/#queries)

[fireEvent](https://testing-library.com/docs/dom-testing-library/api-events/)

[Considerations for fireEvent](https://testing-library.com/docs/guide-events/)

[jest-dom](https://github.com/testing-library/jest-dom?tab=readme-ov-file#tobevisible)