1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-03-01

本記事の内容

React公式ドキュメント推奨のーJestReact Testing LibraryでReduxアプリをテストする際の環境整備を説明する。
react-reduxredux-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)を使用する。デフォルトでは、Jesttesting-library系がすでにインストールされている。(助かる)

// 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>コンポーネントでテスト対象のコンポーネントツリーをラッピングする必要がある。

import { Provider } from "react-redux";

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

 test("+ボタンを押すと数字が1増える", () => {
    // レンダリング
    render(
      <Provider store={store}>
        <Counter />
      </Provider>
    );
  // アクション
  //  ・・・略
  // アサート
  //  ・・・略
  });

しかし、これをケースごとに書いていくと、記述が冗長になってしまう。

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の公式ドキュメントで提案している。

詳細は、本家を覗いてみるとよい。ここで簡単に説明すると、ラッピングの<Provider>コンポーネントを共通化して、レンダリング時にchildrenでテスト対象のコンポーネントを渡すことにしている。

// 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として、上記で共通化できたら、それぞれのテストファイルで下記のように記述する。

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 />);
  // アクション
  //  ・・・略
  // アサート
  //  ・・・略
  });

共通化する前の5行から1行にまとめられて、ずいぶんスッキリしましたね。

ちなみに、preloadedStateでテストの初期値を設定するときは下記のように記述できる。

 test("+ボタンを押すと数字が1プラスされる", () => {
    const initialCount = 5;

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

次回について

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

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

参考記事

Setting Up a Test Environment - redux公式ドキュメント
【React,Redux】JEST+testing-library+MSWで始めるテスト入門

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?