1
2

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.

React Testing Library (RTL) の要素を取得する API の使い分け

Posted at

はじめに

この記事では、React Testing Library (RTL) のページ上の要素を取得する API の使い分け方をサンプルつきで記載していきます。

前提

サンプルを実装した開発環境は以下の通りです。
開発環境は以下の通りです。

  • Windows11
  • VSCode
  • TypeScript 4.9.5
  • React 18.2.0
  • Vite 4.1.0
  • Vitest 0.28.5
  • @testing-library/react 14.0.0

また、事前に以下の記事に記載の手順に沿って、Vitest で React Testing Library が利用できるように設定しておきます。

要素を取得する API の分類

ページ上の要素を取得する API は、以下の3点で分類できます。

  • 取得する要素の数:単一(...By...)、複数(...AllBy...
  • クエリの種類:getBy (getAllBy)...queryBy (queryAllBy)...findBy (findAllBy)...
  • クエリの方法:...ByRole...ByLabelText...ByPlaceholderText...ByText...ByDisplayVaalue...ByAltText...ByTitle...ByTestId

クエリの種類

getBy (getAllBy)queryBy (queryAllBy)findBy (findAllBy) は、以下のように要素が見つからないときの挙動と非同期処理の扱いによって分類できます。

クエリの種類 要素が見つからない 非同期
getBy (getAllBy) エラー ×
queryBy (queryAllBy) null or [] ×
findBy (findAllBy) エラー

getBy (getAllBy) のユースケース

特に理由がなければ(queryBy (queryAllBy)findBy (findAllBy) を使う必要がなければ)、getBy (getAllBy) を利用します。

例えば、単に以下のようなコンポーネント上のテキストを取得するときは、getByText を利用します。

Home.tsx
export const Home = () => {
  return <div>You are home</div>;
};
Home.test.tsx
import { render, screen } from "@testing-library/react";
import { BrowserRouter } from "react-router-dom";
import { describe } from "vitest";
import { Home } from "../../../pages/Home";

describe("Home component", () => {
  test("elements", async () => {
    render(<Home />, { wrapper: BrowserRouter });

    expect(screen.getByText(/you are home/i)).toBeInTheDocument();
  });
});

image.png

queryBy (queryAllBy) のユースケース

queryBy は、要素が存在しない場合、null を返す(queryAllBy の場合、[])ため、存在しない要素に対して利用されます。

例えば、以下のように props の値によって、テキストの表示・非表示が変わる要素があるとします。

Home.tsx
export const Home = ({ authorized }: { authorized: boolean }) => {
  return (
    <>
      {authorized && <div>You are authorized</div>}
    </>
  );
};

この要素のテキストが表示されている場合のテストは、getByText を利用すれば、問題なくテストできます。

Home.test.tsx
describe("Home component", () => {
  test("when authorized", async () => {
    render(<Home authorized={true} />, { wrapper: BrowserRouter });

    expect(screen.getByText(/you are authorized/i)).toBeInTheDocument();
  });
});

image.png

一方、テキストが非表示の場合のテストは、getByText を利用すると、エラーになってしまいます。

Home.test.tsx
test("when not authorized", async () => {
  render(<Home authorized={false} />, { wrapper: BrowserRouter });

  expect(screen.getByText(/you are authorized/i)).not.toBeInTheDocument();
});

image.png

そのため、getByText の代わりに queryBy を利用します。

Home.test.tsx
test("when not authorized", async () => {
  render(<Home authorized={false} />, { wrapper: BrowserRouter });

  expect(screen.queryByText(/you are authorized/i)).not.toBeInTheDocument();
});

エラーが発生することなく、非表示であることのテストができました。
image.png

findBy (findAllBy) のユースケース

findBy (findAllBy) は、要素を非同期に取得することができるため、state の値が変わったことによるUIの変化や非同期APIのレスポンスが返ってきた後のUI表示などに利用できます。

例えば、以下のようにボタンをクリックすることでボタン上のテキストが変わる要素があるとします。

Home.tsx
import { useState } from "react";

export const Home = () => {
  const [buttonText, setButtonText] = useState("button");
  return <button onClick={() => setButtonText("clicked")}>{buttonText}</button>;
};

ボタンクリック後にテキスト表示が変化することをテストする際、getByText を利用するとエラーになってしまいます。

Home.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { BrowserRouter } from "react-router-dom";
import { describe } from "vitest";
import { Home } from "../../../pages/Home";

describe("Home component", () => {
  test("when authorized", async () => {
    render(<Home />, { wrapper: BrowserRouter });

    userEvent.click(screen.getByRole("button"));
    expect(screen.getByText("clicked")).toBeInTheDocument();
  });
});

image.png

一方、findByTextawait と一緒に利用し、非同期に要素を取得すれば、エラーなく、テストをすることができます。

Home.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { BrowserRouter } from "react-router-dom";
import { describe } from "vitest";
import { Home } from "../../../pages/Home";

describe("Home component", () => {
  test("test", async () => {
    render(<Home />, { wrapper: BrowserRouter });

    userEvent.click(screen.getByRole("button"));
    expect(await screen.findByText("clicked")).toBeInTheDocument();
  });
});

image.png

以下の書き方であれば、 getByText を利用してもエラーなく、テストをすることができます。

方法1. クリック処理に await をつける

Home.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { BrowserRouter } from "react-router-dom";
import { describe } from "vitest";
import { Home } from "../../../pages/Home";

describe("Home component", () => {
  test("test", async () => {
    render(<Home />, { wrapper: BrowserRouter });

    await userEvent.click(screen.getByRole("button"));
    expect(screen.getByText("clicked")).toBeInTheDocument();
  });
});

方法2. waitFor を利用する

Home.test.tsx
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { BrowserRouter } from "react-router-dom";
import { describe } from "vitest";
import { Home } from "../../../pages/Home";

describe("Home component", () => {
  test("test", async () => {
    render(<Home />, { wrapper: BrowserRouter });

    userEvent.click(screen.getByRole("button"));
    await waitFor(() => {
      expect(screen.getByText("clicked")).toBeInTheDocument();
    });
  });
});

クエリの方法

RTL は以下の思想をもとに作られています。

The more your tests resemble the way your software is used, the more confidence they can give you.

こちらの思想にもとづき、クエリの方法(By...)は優先度は以下の順番で利用することが推奨されています。

  1. Queries Accessible to Everyone
    1. getByRole
    2. getByLabelText
    3. getByPlaceholderText
    4. getByText
    5. getByDisplayValue
  2. Semantic Queries
    1. getByAltText
    2. getByTitle
  3. Test IDs
    1. getByTestId

参照

1
2
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
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?