【Jest】Reactの選択肢の入力フォームで、GitHub Actionsでの非同期処理テストの問題解決


# 問題　エラー内容
ローカルではテストが通るが、GitHub actionsではテストがエラーになる。

```
GitHub actionsのエラー
選択肢が描画されず、選択肢を選択できないためエラーになる

~~中略~~
FAIL src/__tests__/RegistrationPage.test.tsx (20.002 s)
    TestingLibraryElementError: Value "1" not found in options

    Ignored nodes: comments, script, style
    <select
      class="chakra-native-select__field css-1lldn8"
      data-part="select"
      data-scope="field"
      data-testid="favorite-skill-select"
      id=":r4:"
      name="favoriteSkill"
    >
      <option
        value=""
      >
        選択してください
      </option>
    </select>

      66 |     await waitFor(
      67 |       () => {
    > 68 |         user.selectOptions(selectElement, "1");
         |              ^
      69 |       },
      70 |       { timeout: 5000 },
      71 |     );

```

```
うまくいく場合の選択肢の描画(screen.debug)
debugで選択肢が表示される

<select
  class="chakra-native-select__field css-1lldn8"
  data-part="select"
  data-scope="field"
  data-testid="favorite-skill-select"
  id=":r4:"
  name="favoriteSkill"
>
  <option
    value=""
  >
    選択してください
  </option>
  <option
    value="1"
  >
    React
  </option>
  <option
    value="2"
  >
    TypeScript
  </option>
  <option
    value="3"
  >
    GitHub
  </option>
</select>
```

### 該当箇所

```Register.tsx
  <Field label="好きな技術 *">
    <NativeSelectRoot>
      <NativeSelectField
        {...register("favoriteSkill", {
          required: "好きな技術は必須入力です。",
        })}
        placeholder="選択してください"
        data-testid="favorite-skill-select"
      >
        {skills.map((skill) => (
          <option key={skill.id} value={skill.id}>
            {skill.name}
          </option>
        ))}
      </NativeSelectField>
    </NativeSelectRoot>
    {errors.favoriteSkill && (
      <Text color="red.500">{errors.favoriteSkill.message}</Text>
    )}
  </Field>
```

### エラーになるテスト
```RegistrationPage.test.tsx
import {
  render,
  screen,
  fireEvent,
  waitFor,
  act,
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";

~~中略~~        
await act(async () => {
                const selectElement = await screen.getByTestId("favorite-skill-select");
                await waitFor(() => {
                        userEvent.selectOptions(selectElement, "1");
                }, { timeout: 5000 });
        });

        const registerButton = await waitFor(() =>
                screen.getByTestId("register-button")
        );

        await act(async () => {
                await waitFor(() => {
                        userEvent.click(registerButton);
                });
        });

        await waitFor(() => {
                expect(mockedUsedNavigate).toHaveBeenCalledWith("/");
        }, { timeout: 5000 });
});
```


### テスト修正後
```RegistrationPage.test.tsx 
import {
  render,
  screen,
  fireEvent,
  waitFor,
  act,
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";

~~中略~~

// テストの冒頭で timeout を設定
jest.setTimeout(30000);

~~中略~~
// イベント発火はfireEventに変更している
    await act(async () => {
      fireEvent.change(selectElement, { target: { value: "1" } });
    });
    const registerButton = await waitFor(() =>
      screen.getByTestId("register-button")
    );

    await waitFor(() => {
      fireEvent.click(registerButton);
    });

    await waitFor(
      () => {
        expect(mockInsertUsers).toHaveBeenCalled();
        expect(mockInsertUserSkill).toHaveBeenCalled();
        expect(mockedUsedNavigate).toHaveBeenCalledWith("/");
      },
      { timeout: 5000 }
    );
```


### await, waitFor, actの動作
await, waitFor, actについ(copilotの解説)

await: 
Promiseが解決または拒否されるまで待機し、その結果を返します。
async 関数内でのみ使用できます。

waitFor: 
非同期操作が完了するまで待機し、特定の条件が満たされるまでポーリングを行います。
主に、非同期で更新されるDOM要素の存在を確認するために使用されます。

act: 
Reactの状態やエフェクトが更新される操作をラップし、その後のレンダリングが完了するまで待機します。
状態の変更やエフェクトの実行を伴う操作をテストするために使用されます。


# おわりに
ローカルでは通るテストが、GitHub actionsでは通らなかったので困りました。
非同期処理は難しいので、どうすればテスト要件を満たせるのか考えるようにします。