Redux テストで学んだ教訓: Real Store を使用してコンポーネントをレンダリングする


この投稿は、redux アプリをテストするための 2 つの重要な原則、「切断されたコンポーネントのテストを停止する」と「小さなユーティリティ ライブラリを構築する」について説明した記事の続きです.ほとんどの場合、Redux アプリのテストをより管理しやすくするために使用する主要なユーティリティのいくつかを調べました.この投稿では、最初の投稿で取り上げた別の領域について説明します: Real Store を使用してコンポーネントをレンダリングする

長い間、私は redux mock store に依存して、テスト中にレンダリングするためにコンポーネントにデータをプリロードしていました.このアプローチにより、任意のデータを含むコンポーネントのスナップショットを簡単に作成し、正しくレンダリングされることを確認できます.完全に失敗するのは、相互作用のテストです.

閉じるボタンをクリックするか、その画像を選択するとどうなりますか? redux mock store には、getActions という名前の特別なメソッドがあり、どのアクションが起動されたかを教えてくれますが、それだけです.これらのアクションは実際にはレデューサーに到達せず、UI を更新することはありません.これにより、テストを書くのがかなりイライラします.コンポーネントがある状態から別の状態に遷移できることを確認する良い方法はありません.スナップショットのみをテストできます.

これを解決する最も簡単な方法は、テストをラップするために使用する <Provider> に実際の redux ストアを渡し、それを返すことです.例えば:

import { render } from "@testing-library/react";
import { store } from "../app/store";

function renderWithContext(element) {
    render(
      <Provider store={store}>{element}</Provider>
   );
   return { store };
}


これはすぐにあなたにあらゆる種類の力を与えます. 1 つ目は、アクションをディスパッチしてデータを入力したり、redux ストアを変更したりする機能です.これらのアクションは同期的にディスパッチされるため、UI が更新されたことをすぐにアサートできます.

test("table should render all kinds of data", () => {
    const { store } = renderWithContext(<ResultsTable />);
    // expect() table to be empty
    store.dispatch({ type: "POPULATE_DATA", data: { /* ... */ })
    // expect() table to be full
});


他にできることは、テストしているコンポーネントに通常は影響を与えないイベントに応答して、redux ストアが変更されたことをアサートすることです.たとえば、カウンターを更新するボタンがあり、そのカウンター コンポーネントが別の場所にあるとします.ボタンをクリックすると、ストアのカウントが更新されることを簡単にテストできます.

test("counter should update count", () => {
    const { store } = renderWithContext(<CounterButton />);
    expect(store.getState().count).toEqual(0);
    userEvent.click(screen.getByRole("button"));
    expect(store.getState().count).toEqual(1);
});


実際の redux ストアを共有する際の問題は、テストの順序は重要ではないということです.テストを分離して実行したい場合.共有ストア アプローチでは、1 つのテストでイベントをディスパッチすると、変更が将来のすべてのテストに反映されます.そのため、重要なユーティリティとして示した getStoreWithState メソッドにたどり着きました.

// ...
export const store = configureStore({ reducer });
export function getStoreWithState(preloadedState) {
  return configureStore({ reducer, preloadedState });
}


ここには 2 つの重要な部分があります.前に述べたのは preloadedState オプションで、特定の方法で既にセットアップされた状態でコンポーネントをテストでレンダリングできます (モック redux ストアに似ています).ここでの 2 番目のより微妙な成果は、生成されたストアに、アプリのストアで使用されるのと同じレデューサーへのアクセスを許可していることです.これにより、各テストに使用する分離されたストアが提供され、アプリケーションのレデューサーの全機能にもアクセスできます.

このアプローチの利点の 1 つは、redux にフックされたコンポーネントをテストするたびに、複数のレデューサーもテストしていることです.これはより経済的であり、アプリケーションが実際にどのように機能するかをより正確に反映しています.言うまでもなく、この方法でテストを作成する方がはるかに簡単です. mock-redux-store でのテストに慣れている場合、このアプローチは非常に効果的です.

Redux アプリケーションをテストするための私のアプローチについて詳しく知りたい場合は、私のコース Confidently Testing Redux Applications with Jest and TypeScript をご覧ください.