JestとRTL(React Testing Library)でReduxアプリを自動テストする環境整備

## 本記事の内容
[React公式ドキュメント](https://ja.reactjs.org/docs/testing.html)推奨のー[Jest](https://jestjs.io/ja/)と[React Testing Library](https://testing-library.com/docs/react-testing-library/intro/)でReduxアプリをテストする際の環境整備を説明する。
`react-redux`、`redux-toolkit`どちらの書き方でも同様に使える。

## 前提条件

React: "^18.2.0"
react-redux: "^8.0.5"
@reduxjs/toolkit: "^1.9.1"
@testing-library/jest-dom: "^5.16.5"
@testing-library/react: "^13.4.0"
@testing-library/user-event: "^14.4.3"
@types/jest: "^29.4.0"

## 環境整備

今回は、[CRA(create-react-app)](https://create-react-app.dev/docs/getting-started)を使用する。デフォルトでは、`Jest`や`testing-library`系がすでにインストールされている。（助かる）
```javascript
// package.json
 "dependencies": {
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "@types/jest": "^27.5.2",
  // ・・・省略
   },
```

### Reduxアプリをテストするために
さて、Reduxを含めたアプリをテストするために、`React-Redux`がexportしている`<Provider>`コンポーネントでテスト対象のコンポーネントツリーをラッピングする必要がある。

```typescript
import { Provider } from "react-redux";

import { store } from './store';
import Counter from ".";

 test("+ボタンを押すと数字が1増える", () => {
    // レンダリング
    render(
      <Provider store={store}>
        <Counter />
      </Provider>
    );
  // アクション
  //  ・・・略
  // アサート
  //  ・・・略
  });
```
しかし、これをケースごとに書いていくと、記述が冗長になってしまう。
```typescript
import { Provider } from "react-redux";

import { store } from './store';
import Counter from ".";

 test("+ボタンを押すと数字が1プラスされる", () => {
    // レンダリング
    render(
      <Provider store={store}>
        <Counter />
      </Provider>
    );
  // アクション
  //  ・・・略
  // アサート
  //  ・・・略
  });
 test("-ボタンを押すと数字が1マイナスされる", () => {
    // レンダリング
    render(
      <Provider store={store}>
        <Counter />
      </Provider>
    );
  // アクション
  //  ・・・略
  // アサート
  //  ・・・略
  });
```
この冗長な記述を簡略化するために、`<Provider>`を共通化しようじゃないか！という素晴らしい？方法を[Reduxの公式ドキュメント](https://redux.js.org/usage/writing-tests#setting-up-a-reusable-test-render-function)で提案している。

詳細は、本家を覗いてみるとよい。ここで簡単に説明すると、ラッピングの`<Provider>`コンポーネントを共通化して、レンダリング時に`children`でテスト対象のコンポーネントを渡すことにしている。
```typescript
// utils/test-utils.tsx
import React, { PropsWithChildren } from 'react'
import { render } from '@testing-library/react'
import type { RenderOptions } from '@testing-library/react'
import { configureStore } from '@reduxjs/toolkit'
import type { PreloadedState } from '@reduxjs/toolkit'
import { Provider } from 'react-redux'

import type { AppStore, RootState } from '../app/store'
// As a basic setup, import your same slice reducers
import counterReducer from '../features/counter/counterSlice'

// This type interface extends the default options for render from RTL, as well
// as allows the counter to specify other things such as initialState, store.
interface ExtendedRenderOptions extends Omit<RenderOptions, 'queries'> {
  preloadedState?: PreloadedState<RootState>
  store?: AppStore
}

export function renderWithProviders(
  ui: React.ReactElement,
  {
    preloadedState = {},
    // Automatically create a store instance if no store was passed in
    store = configureStore({ reducer: { counter: counterReducer }, preloadedState }),
    ...renderOptions
  }: ExtendedRenderOptions = {}
) {
  function Wrapper({ children }: PropsWithChildren<{}>): JSX.Element {
    return <Provider store={store}>{children}</Provider>
  }

  // Return an object with the store and all of RTL's query functions
  return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) }
}
```
また、`preloadedState`で、`store`の初期値をテストごとに設定できるので、大変便利ではないかなと感じている。

共通の`util`として、上記で共通化できたら、それぞれのテストファイルで下記のように記述する。
```typescript
import { Provider } from "react-redux";

import { renderWithProviders } from '../utils/test-utils';
import { store } from './store';
import Counter from ".";

 test("+ボタンを押すと数字が1プラスされる", () => {
    // レンダリング
    renderWithProviders(<Counter />);
  // アクション
  //  ・・・略
  // アサート
  //  ・・・略
  });
 test("-ボタンを押すと数字が1マイナスされる", () => {
    // レンダリング
    renderWithProviders(<Counter />);
  // アクション
  //  ・・・略
  // アサート
  //  ・・・略
  });
```
共通化する前の５行から1行にまとめられて、ずいぶんスッキリしましたね。

ちなみに、`preloadedState`でテストの初期値を設定するときは下記のように記述できる。
```typescript
 test("+ボタンを押すと数字が1プラスされる", () => {
    const initialCount = 5;

    // レンダリング
    renderWithProviders(<Counter />, {
    preloadedState: {
      currentCount: initialCount
    }
  });
  // アクション
  //  ・・・略
  // アサート
  //  ・・・略
  });
```


## 次回について
今回は、`Redux`アプリの自動テスト環境構築をやってみた。
次回は、`RTK Query`と`msw`使用した非同期通信をテストする場合の追加設定の記事を作成していく予定でござる。

最後までお読みいただきありがとうございました。

## 参考記事
[Setting Up a Test Environment - redux公式ドキュメント](https://redux.js.org/usage/writing-tests#setting-up-a-test-environment)
[【React,Redux】JEST+testing-library+MSWで始めるテスト入門](https://zenn.dev/yuki_tu/articles/bdd942df59fb69)