はじめに
Jest
を活用したテストで、React Router
の useNavigate
をモックし、MemoryRouter
を活用してテストする方法についてまとめます。
問題点
戻るボタン押下時のページ遷移のテストを実施したいと考えて、以下のテストを作成しました。
UserCard.test.tsx
it('ホーム画面に遷移する', async () => {
render(
<BrowserRouter>
<ChakraProvider>
<UserCard />
</ChakraProvider>
</BrowserRouter>
);
// 戻るボタン押下時のテスト
const backButton = await screen.findByTestId('back-button');
await user.click(backButton);
await expect(mockNavigate).toHaveBeenCalledWith('/');
});
しかし、このテストではエラーが発生しました。
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: "/"
Number of calls: 0
58 | user.click(backButton);
59 |
> 60 | await expect(mockNavigate).toHaveBeenCalledWith('/');
| ^
61 | });
62 | });
63 |
原因と解決方法
元々は BrowserRouter
を使用していましたが、テスト環境では MemoryRouter
の方が適しているようです。
というのも、実際のブラウザ動作を必要としないユニットテストやコンポーネントテストでは、MemoryRouter
を使うのが一般的らしい。
そのため、MemoryRouter
の initialEntries
を使って、意図したパスでコンポーネントをレンダリングすることで解決できました。
UserCard.test.tsx
render(
<MemoryRouter initialEntries={['/cards/testuser']}>
<ChakraProvider>
<UserCard />
</ChakraProvider>
</MemoryRouter>
);
initialEntries のポイント
- 特定のパスでコンポーネントをレンダリングできる
- ルートパラメータ(例:testuser)を設定できる
- 実際のブラウザ URL を変更せず、ルーティングコンテキストを提供できる
また useNavigate
もモック化する必要があります。
const mockNavigate = jest.fn();
jest.mock('react-router', () => ({
...jest.requireActual('react-router'),
useNavigate: () => mockNavigate,
}));
useNavigateをモック化するポイント
- react-router モジュールの useNavigate だけをモック化して、それ以外の関数等はそのまま使用可能
- useNavigate が呼び出されたことを監視できる
さいごに
ページ遷移のテストで、useNavigateとreact router をモック化する正しい方法を学ぶことができました。
参考サイト