React コンポーネントのテストを書いていると、 テストの文脈に関係のない props を毎回渡すのが面倒… と思ったことはありませんか?
今回は、そうした課題を解決するために段階的に進化させていったテスト用の関数と、最終的にたどり着いた renderWithParams
という関数を紹介します。
1. 最初のヘルパー関数
例として、次のようなコンポーネントを想定します:
function ExampleComponent(props: {
prop1: string
prop2: string
prop3: number
prop4: (_: string) => void
}) {
return <></>
}
テストごとに関係のない props を書きたくない
たとえば prop1
に関するテストだけを書きたいときでも、他の prop を省略できず、すべて書かなければいけません。
render(
<ExampleComponent
prop1="テストに関係のある値"
prop2=""
prop3={0}
prop4={() => {}}
/>
)
そこで最初に作ったのが、このようなヘルパー関数です:
function renderExampleComponent(params: Partial<ComponentProps<typeof ExampleComponent>> = {}) {
render(
<ExampleComponent
prop1={params?.prop1 ?? ''}
prop2={params?.prop2 ?? ''}
prop3={params?.prop3 ?? 0}
prop4={params?.prop4 ?? (() => {})}
/>
)
}
これにより、必要な props だけを渡すことでテストが書けるようになり、コードがスッキリしました。
2. rerender したくなった時の問題再発
ある時、rerender
を使いたくなり、次のように関数を拡張しました:
function renderExampleComponent(params: Partial<ComponentProps<typeof ExampleComponent>> = {}): RenderResult {
return render(
<ExampleComponent
prop1={params?.prop1 ?? ''}
prop2={params?.prop2 ?? ''}
prop3={params?.prop3 ?? 0}
prop4={params?.prop4 ?? (() => {})}
/>
)
}
しかし、この場合 rerender
のたびにまた全ての props を書かないといけなくなり、本来の目的と逆行してしまいます。
const { rerender } = renderExampleComponent({ prop1: 'value1' })
rerender(<ExampleComponent prop1="updated" prop2="" prop3={0} prop4={() => {}} />)
3. rerender にも対応したヘルパー関数に進化
そこで rerender
のラッパーも返すように関数を再度アップデートしました:
function renderExampleComponent(params: Partial<ComponentProps<typeof ExampleComponent>> = {}) {
const { rerender } = render(
<ExampleComponent
prop1={params?.prop1 ?? ''}
prop2={params?.prop2 ?? ''}
prop3={params?.prop3 ?? 0}
prop4={params?.prop4 ?? (() => {})}
/>
)
return {
rerender: (params: Partial<ComponentProps<typeof ExampleComponent>> = {}) => {
rerender(
<ExampleComponent
prop1={params?.prop1 ?? ''}
prop2={params?.prop2 ?? ''}
prop3={params?.prop3 ?? 0}
prop4={params?.prop4 ?? (() => {})}
/>
)
}
}
}
これにより、以下のように rerender
でも関係ある props だけ渡せるようになりました:
const { rerender } = renderExampleComponent({ prop1: 'value1' })
rerender({ prop1: 'value1 on 2nd rendering' })
4. ただし、今度はこのロジック自体が重複しはじめた
複数のコンポーネントで同様の構造の renderXxx
を作るうちに、「この初期 props を使って rerender
を再構成するパターン、また書いてるな…」と気づきます。
ここで、ようやく抽象化のフェーズに突入します。
5. 汎用的に使えるように renderWithParams
を導入
そうして生まれたのが、次の renderWithParams
関数です。
export const renderWithParams = <T extends object>(
buildComponent: (params?: T) => ReactNode,
paramsOf1stRendering?: T,
) => {
const { rerender, unmount } = render(buildComponent(paramsOf1stRendering))
return {
rerender: (params?: T) => {
rerender(buildComponent(params))
},
unmount,
}
}
使用例
const buildComponent = (params?: Partial<ComponentProps<typeof ExampleComponent>>) => (
<ExampleComponent
prop1={params?.prop1 ?? ''}
prop2={params?.prop2 ?? ''}
prop3={params?.prop3 ?? 0}
prop4={params?.prop4 ?? (() => {})}
/>
)
const { rerender } = renderWithParams(buildComponent, { prop1: 'initial' })
rerender({ prop1: 'updated' })
6. 導入してよかったこと
- ✅ props の差分だけ指定して再レンダリングできる
- ✅ 関係ない props を記述しなくて済む
- ✅
rerender
のラップ処理を共通化できる - ✅ テストが読みやすく、意図が明確に