お知らせ

現在サイトのリニューアル作業中のため、全体的にページの表示が乱れています。

確認環境

  • historyのバージョンとreact-router-domのバージョンが上手く噛み合ってないと既存実装で型エラーが出る可能性がある
Env Ver
react 17.0.2
react-dom 17.0.2
react-router-dom 5.2.0
react-scripts 4.0.3
typescript 4.2.3
history 4.10.1

サンプルコード

  • ToPageに飛ばしたlocation.stateを検査する内容

ToPage.tsx

export const ToPage = () => {
  const location = useLocation<{ test?: string }>();
  return <p>from {location.state?.test ?? 'nothing'}</p>;
};

ToPage.spec.tsx

const renderWithRouter = (ui: JSX.Element, { route = '/' } = {}) => {
  window.history.pushState({}, 'Test page', route);

  return render(ui, { wrapper: BrowserRouter });
};

describe('onload', () => {
  it('from history.push', () => {
    const history = createMemoryHistory<{ test: string }>();
    history.push(AppRoute.to.path, { test: 'foo' });
    const { container } = renderWithRouter(
      <Router history={history}>
        <ToPage />
      </Router>
    );

    expect(container).toHaveTextContent('from foo');
    expect(history.location.state.test).toBe('foo');
  });
});
  • まずhistory.push('/path/to/dest')自体のテストを書くことはないはずなので、何かしらの副作用として実行される事が前提です
  • testing-library.com に書いてあることの応用です
/**
 * useHistory() を使えるようにするテストユーティリティ
 */
const renderWithRouter = (children: JSX.Element, { route = '/' } = {}) => {
  window.history.pushState({}, 'Test page', route);

  return render(children, {
    wrapper: BrowserRouter,
  });
};

// spy 用のモック関数
const mockPush = jest.fn();
// react-router-dom をモックにする
jest.mock('react-router-dom', () => ({
  useHistory: () => ({
    // モック関数を設定
    push: mockPush,
  }),
}));

describe('Bar', () => {
  it('history.push のテスト', () => {
    renderWithRouter(<Bar />, {
      route: '/foo/bar',
    });

    // ここらへんに同期的に副作用を起こす処理を書く
    // 以降、副作用で history.push('/foo/baz') されたとする

    // 呼ばれたことの確認
    expect(mockPush).toBeCalled();
    // 引数の確認
    expect(mockPush.mock.calls[0][0]).toBe('/foo/baz');
  });
});

フックを単体でテストするケースを想定。

このパターンはコンポーネントからフックを切り離しているケースで有用。手法としては@testing-library/react-hooksrenderHook()を使う。

カスタムフック

export const useUserForm = () => {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');

  const onChangeUserName = (ev: string) => {
    setUsername(ev);
  };
  const onChangePassword = (ev: string) => {
    setPassword(ev);
  };

  return {
    username,
    password,
    onChangeUserName,
    onChangePassword,
  };
};

テストコード

it('onChangeUserName で username が設定されること', () => {
  // `renderHook` で Hook をレンダリング
  const { result } = renderHook(() => useUserForm());
  // `act()` で Hook のイベントを叩く
  act(() => result.current.onChangeUserName('foo'));
  // 結果を見る
  expect(result.current.username).toBe('foo');
});