OITA: Oika's Information Technological Activities

@oika 情報技術的活動日誌。

react-query : useQuery への依存をスタブに差し替えてコンポーネントテスト

引き続き React Query の話。

ある関数コンポーネントが useQuery によって取得される外部データに依存している場合に、そのuseQueryで取得される外部データをダミーに置き換えて、コンポーネントテストを書く方法。
なかなかまとまった情報が見つからなかったので記録しておく。

テストには Jest & React Testing Library を使用。

テストしたいコンポーネントのイメージとして、外部APIからユーザ情報を取得して表示するだけの部品を考える。
↓こういうやつ。

/**
 * APIのデータ取得ロジック
 */
const fetchUserData = async () => {
  const res = await fetch("https://jsonplaceholder.typicode.com/users");
  if (!res.ok) {
    throw new Error("fetch error");
  }
  return res.json();
};

/**
 * users API から取得した情報を表示するコンポーネント
 */
export const DataViewer = () => {
  const { data, isError, isLoading } = useQuery("users", fetchUserData);

  if (isLoading) return <div>loading...</div>;
  if (isError || data == null) return <div>ERR!</div>;

  const userCount = data.length;
  const firstUser = `id=${data[0].id}, name=${data[0].name}`;

  return (
    <div>
      {userCount}人のユーザ(1人目:{firstUser})
    </div>
  );
};

f:id:kd1:20210919105723p:plain

アプリケーションに組み込む際は、上位コンポーネントとして QueryClientProvider をおく必要がある。

export default function App() {
  const client = new QueryClient();

  return (
    <div>
      <QueryClientProvider client={client}>
        <DataViewer />
      </QueryClientProvider>
    </div>
  );
}

さて、これのコンポーネントテストを書く際、外部から取得されるユーザ情報をダミーデータに置き換えて、取得されたデータが正しく表示される部分だけをテストしたいとする。

test("ユーザ情報を表示する", () => {

  //↓こいつにダミーデータを渡したい
  const dom = render(
      <DataViewer />
  );

  expect(dom.baseElement.textContent).toBe("(期待値)");
});

テストコード内でrenderする際は、テスト用の QueryClient を定義し、QueryClientProvider に渡してテスト用のコンポーネントをラップする。

この QueryClient に対し、setQueryData を使って手動で任意のデータを設定することができる。
その上で、データの再取得が走らないように staleTime を Infinity などにしておけば良い。

test("ユーザ情報を表示する", () => {
  //テスト用のclientを定義する
  const clientStub = new QueryClient({
    defaultOptions: {
      queries: {
        retry: false,
        //※refetchが走らないように
        staleTime: Infinity
      }
    }
  });
  //手動でデータを設定
  clientStub.setQueryData("users", [
    { id: 555, name: "first user" },
    { id: 666, name: "another user" }
  ]);

  //テストしたいコンポーネントを QueryClientProvider でラップ
  const dom = render(
    <QueryClientProvider client={clientStub}>
      <DataViewer />
    </QueryClientProvider>
  );

  expect(dom.baseElement.textContent).toBe(
    "2人のユーザ(1人目:id=555, name=first user)"
  );
});

以上。

コンポーネントを単独でテストしやすく保つにはどちらかというとpropsバケツリレーが基本かと思っていたが、これができるならカジュアルに各コンポーネントから useQuery しちゃってもいいかもですね。

コード全体は以下で確認できます。