背景
Chakra UI の Provider(ChakraProvider)を利用しているプロジェクトで、React Testing Library のテストを書いていたら、テスト環境では Provider が存在せずコンポーネントが正しく動かない問題にぶつかった
毎回テストファイルで ChakraProvider を書くのは DRY に反するので、カスタムレンダー(Custom Render)を作って解決した
カスタムレンダーとは
render(<Component />) と書くだけで、裏で自動的に Provider が全部付いてくるようにするラッパー関数
本番では App.tsx が Provider 群で全体を囲んでいるが、テストでは App を経由せず直接コンポーネントをレンダリングする。そのため Provider が存在しない状態になり、テーマや i18n が動かない。カスタムレンダーは、テスト用にも同じ Provider で囲む仕組みを提供する
実装の全体像
AllTheProviders コンポーネント
const AllTheProviders = ({children}: {children: React.ReactNode}) => {
return (
<ThemeProvider>
<TranslationProvider>
{children}
</TranslationProvider>
</ThemeProvider>
)
}
-
({children}: {children: React.ReactNode})は JS の分割代入と TypeScript の型注釈を同時にやっている-
{children}→ props オブジェクトからchildrenプロパティだけ取り出す(分割代入) -
: {children: React.ReactNode}→ TypeScript の型注釈。React.ReactNodeは React が描画できるもの全般の型(文字列、数値、JSX、配列、nullなど)
-
-
childrenとは、コンポーネントのタグの中に挟んだものが自動的に渡される仕組み
<AllTheProviders>
<MyComponent /> ← これがchildren
</AllTheProviders>
customRender 関数
const customRender = (
ui: ReactElement,
options?: Omit<RenderOptions, 'wrapper'>,
) => render(ui, {wrapper: AllTheProviders, ...options})
引数を一つずつ分解する:
-
ui: ReactElement→ テストしたい JSX コンポーネント -
options?: Omit<RenderOptions, 'wrapper'>の分解:-
?→ 省略可能(オプショナルパラメータ) -
RenderOptions→ RTL のrenderが受け取るオプションの型 -
Omit<RenderOptions, 'wrapper'>→ RenderOptions からwrapperプロパティだけ除外した型。カスタムレンダーはwrapper: AllTheProvidersを強制する設計なので、外からの上書きを型レベルで禁止している
-
-
{wrapper: AllTheProviders, ...options}→ wrapper は常に AllTheProviders を使い、その他のオプションはスプレッド構文で展開して渡す
エクスポートのパターン
export * from '@testing-library/react' // 全APIをそのまま再エクスポート
export {customRender as render} // renderだけカスタム版で上書き
これにより、テスト側の import 元を @testing-library/react → test-utils に変えるだけで、Provider が自動的に付いてくる
まとめ
- カスタムレンダーは「テストで
render(<Component />)と書くだけで Provider が全部付いてくるラッパー関数」 -
Omit<RenderOptions, 'wrapper'>で wrapper の上書きを型レベルで禁止するのがポイント - テスト側の import 元を
test-utilsに変えるだけで、Provider が自動的に付いてくる
感想
- テスト環境との違いがよくわかったので結構良い調査だった