テストで毎回全部の props 書いてる？不要な props を省いてテストを書くための関数を作った話

React コンポーネントのテストを書いていると、 **テストの文脈に関係のない props を毎回渡すのが面倒…** と思ったことはありませんか？

今回は、そうした課題を解決するために段階的に進化させていったテスト用の関数と、最終的にたどり着いた `renderWithParams` という関数を紹介します。


## 1. 最初のヘルパー関数

例として、次のようなコンポーネントを想定します：

```tsx
function ExampleComponent(props: {
  prop1: string
  prop2: string
  prop3: number
  prop4: (_: string) => void
}) {
  return <></>
}
```

### テストごとに関係のない props を書きたくない

たとえば `prop1` に関するテストだけを書きたいときでも、他の prop を省略できず、すべて書かなければいけません。

```tsx
render(
  <ExampleComponent
    prop1="テストに関係のある値"
    prop2=""
    prop3={0}
    prop4={() => {}}
  />
)
```


そこで最初に作ったのが、このようなヘルパー関数です：

```tsx
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` を使いたくなり、次のように関数を拡張しました：

```tsx
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 を書かないといけなくなり、本来の目的と逆行してしまいます。

```tsx
const { rerender } = renderExampleComponent({ prop1: 'value1' })

rerender(<ExampleComponent prop1="updated" prop2="" prop3={0} prop4={() => {}} />)
```


## 3. rerender にも対応したヘルパー関数に進化

そこで `rerender` のラッパーも返すように関数を再度アップデートしました：

```tsx
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 だけ渡せるようになりました：

```ts
const { rerender } = renderExampleComponent({ prop1: 'value1' })

rerender({ prop1: 'value1 on 2nd rendering' })
```


## 4. ただし、今度はこのロジック自体が重複しはじめた

複数のコンポーネントで同様の構造の `renderXxx` を作るうちに、「この初期 props を使って `rerender` を再構成するパターン、また書いてるな…」と気づきます。

ここで、ようやく抽象化のフェーズに突入します。


## 5. 汎用的に使えるように `renderWithParams` を導入

そうして生まれたのが、次の `renderWithParams` 関数です。

```ts
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,
  }
}
```

### 使用例

```tsx
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` のラップ処理を共通化できる**
- ✅ **テストが読みやすく、意図が明確に**