next/navigationのhooksのモック

## これはなに
jestでのテスト時に、next/navigationのhooks (useRouter, usePathname, useSearchParams)のモックで困っている人が一定数いそうな気がしたので、解決方法を書いてみます。

## 結論
こんな感じでモックしたプロバイダーを作って
```tsx
// app-router-context-provider-mock.tsx

import {
  AppRouterContext,
  AppRouterInstance,
} from 'next/dist/shared/lib/app-router-context.shared-runtime';
import React from 'react';

type AppRouterContextProviderMockProps = {
  router?: Partial<AppRouterInstance>;
  children: React.ReactNode;
};

export const AppRouterContextProviderMock = ({
  router,
  children,
}: AppRouterContextProviderMockProps): React.ReactNode => {
  const mockedRouter: AppRouterInstance = {
    back: jest.fn(),
    forward: jest.fn(),
    push: jest.fn(),
    replace: jest.fn(),
    refresh: jest.fn(),
    prefetch: jest.fn(),
    ...router,
  };
  return (
    <AppRouterContext.Provider value={mockedRouter}>
      {children}
    </AppRouterContext.Provider>
  );
};
```
```tsx
// pathname-context-provider-mock.tsx

import { PathnameContext } from 'next/dist/shared/lib/hooks-client-context.shared-runtime';
import { ReactNode } from 'react';

type PathNameContextProviderMockProps = {
  pathname: string;
  children: ReactNode;
};

export const PathNameContextProviderMock = ({
  pathname,
  children,
}: PathNameContextProviderMockProps): ReactNode => {
  return (
    <PathnameContext.Provider value={pathname}>
      {children}
    </PathnameContext.Provider>
  );
};
```
```tsx
// search-params-context-provider-mock.tsx

import { SearchParamsContext } from 'next/dist/shared/lib/hooks-client-context.shared-runtime';

type SearchParamsContextProviderMockProps = {
  children: React.ReactNode;
  searchParams: URLSearchParams;
};

export const SearchParamsContextProviderMock = ({
  searchParams,
  children,
}: SearchParamsContextProviderMockProps) => {
  return (
    <SearchParamsContext.Provider value={searchParams}>
      {children}
    </SearchParamsContext.Provider>
  );
};
```

テストで使えば良いです。
```tsx
// hoge.spec.tsx

import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime';
import { AppRouterContextProviderMock } from './app-router-context-provider-mock';
import { PathNameContextProviderMock } from './pathname-context-provider-mock';
import { SearchParamsContextProviderMock } from './search-params-context-provider-mock';
import { render, screen } from '@testing-library/react';

type Props = {
  push: AppRouterInstance["push"];
  pathname: string;
  searchParams: URLSearchParams;
}

describe('なんかのテスト', () => {
  const WrappedElement = ({push, pathname, searchParams}: Props) => (
    <AppRouterContextProviderMock router={{push}}>
      <PathNameContextProviderMock pathname={pathname}>
        <SearchParamsContextProviderMock searchParams={searchParams}>
          <テストしたいコンポーネント />
        </SearchParamsContextProviderMock>
      </PathNameContextProviderMock>
    </AppRouterContextProviderMock>
  );

  it('should render correctly', () => {
    const push = jest.fn();
    const searchParams = new URLSearchParams();
    searchParams.set('p', 'test');
    render(<WrappedElement push={push} pathname='/hoge' searchParams={searchParams} />);
    // 以下省略
  });
});
```

## なぜこれで動くの？
Next.jsのコードを読むと、それぞれコンテキストからこれらのデータを取ってきていることが分かります。

https://github.com/vercel/next.js/blob/db153fbcd60d8f1265ec09a63ddcd7a82e9f5a2b/packages/next/src/client/components/navigation.ts

これら、使われているコンテキストのproviderでモックをセットしてあげることで、テスト実行時にモックが使われます。

## 参考
手前味噌ですが、こちらに書いた内容をちょっと見直したものです。

https://github.com/vercel/next.js/discussions/48937#discussioncomment-6395245