React Testing Libraryにチャレンジした3 現在進行形

7 min read読了の目安(約6700字

前回の記事はこちら

https://zenn.dev/k_matsumoto/articles/e9df97449eb50c

今日学んだ新しいテスト
・配列
・toEqual
・toBeNull

リストが上手くいくかどうかのテスト

配列の要素がない時のテスト
配列の要素がある時のテスト

propsに配列を渡してあげるアプリケーションにする必要があるようです。

Should render No data ! when no data proppedはない時
Should render list item correctlyはある時

データがない時

データがない時はtoBeInTheDocumentでドキュメント全体にNo data !ってあるよねとします。

  it("Should render No data ! when no data propped", () => {
    render(<FreamworkList />);
    expect(screen.getByText("No data !")).toBeInTheDocument();
  });

データがある時

データがある時は配列があっているか確認します。
ダミーの配列データを準備します。

getAllByRoleでタグのロールを探します。
liはlistitemになります。
そのgetAllByRoleで検索すると配列で取得することができます。
それをmap関数で配列を作り直します。
各配列の要素をeleとして
ele.textContentとして要素に格納された値を取得して再度配列に格納します。

するとdummyDataで作成したものと取得したものがあっているかを確認できます。
さらにデータが入っているのでNo data !がないかをqueryByTextで検索します。

こう言った感じで練習していくと上手くなるのかなとは思いました。

 it("Should render list item correctly", () => {
    const dummyData = [
      { id: 1, item: "React dummy" },
      { id: 2, item: "Angular dummy" },
      { id: 3, item: "Vue dummy" },
    ];
    render(<FreamworkList frameworks={dummyData} />);
    const frameworkItems = screen
      .getAllByRole("listitem")
      .map((ele) => ele.textContent);
    const dummyItems = dummyData.map((ele) => ele.item);
    expect(frameworkItems).toEqual(dummyItems);
    expect(screen.queryByText("No data !")).toBeNull();
  });

useEffectのテスト

axiosでapiの値を取得して
取得したらpタグの中に表示させるというアプリケーションになります。

import React from "react";
import axios from "axios";

const UseEffectRender = () => {
  const [user, setUser] = React.useState(null);
  const fetchJSON = async () => {
    const res = await axios.get("https://jsonplaceholder.typicode.com/users/1");
    return res.data;
  };
  React.useEffect(() => {
    const fetchUser = async () => {
      const user = await fetchJSON();
      setUser(user);
    };
    fetchUser();
  }, []);

  return (
    <div>
      {user ? (
        <p>
          I am {user.username} : {user.email}
        </p>
      ) : null}
    </div>
  );
};

export default UseEffectRender;

テストすべき内容は
ページレンダリング時はpタグは存在しないこと
apiの通信で取得できたらあpタグのドキュメントにI amが存在すること
の2点になります。

ポイント

async/awaitを使用することでapiの取得するまで待つことができます。

import React from "react";
import { render, screen } from "@testing-library/react";
import UseEffectRender from "./UseEffectRender";

describe("useEffect rendering", () => {
  it("Should render only after async function resolved", async () => {
    render(<UseEffectRender />);
    expect(screen.queryByText(/I am/)).toBeNull();
    expect(await screen.findByText(/I am/)).toBeInTheDocument();
  });
});

API Mockコンポーネント

apiのモックを作るときはmock server workerを使用します

npm i msw --save-dev

機能としては
ボタンを押すとaxiosでデータをgetする
データが取得できればそのデータを表示ボタンは非活性
データが取得できなければエラーメッセージとボタンは活性

import React from "react";
import axios from "axios";

const MockServer = () => {
  const [clicked, setClicked] = React.useState(false);
  const [username, setUsername] = React.useState("");
  const [error, setError] = React.useState("");

  const fetchUser = async () => {
    axios
      .get("https://jsonplaceholder.typicode.com/users/1")
      .then((res) => {
        const { username } = res.data;
        setUsername(username);
        setClicked(true);
      })
      .catch(() => {
        setError("Fetching Failed!");
      });
  };

  const buttonText = clicked ? "Loaded" : "Start Fetch";
  return (
    <div>
      <button onClick={fetchUser} disabled={clicked}>
        {buttonText}
      </button>
      {username && <h3>{username}</h3>}
      {error && <p data-testid="error">{error}</p>}
    </div>
  );
};

export default MockServer;

mswからrestとsetupServerをインポート

まずは擬似的なAPIのサーバーを作る

// 擬似的なAPIのサーバーを作る
const server = setupServer(
  rest.get("https://jsonplaceholder.typicode.com/users/1", (req, res, ctx) => {
    return res(ctx.status(200), ctx.json({ username: "Bred dummy" }));
  })
);

サーバーを起動しておく

// 擬似サーバーを起動しておく
beforeAll(() => server.listen());

サーバーの中身はテストごとにリセットする

afterEach(() => {
  server.resetHandlers();
  cleanup();
});

テスト終了後サーバーを閉じる

afterAll(() => server.close());

テスト1はapiを取得するとテキストが取得できているのとボタンが非活性になっていること

it("[Fetch success] Should display fetch data correctly and button disable", async () => {
    render(<MockServer />);
    userEvent.click(screen.getByRole("button"));
    expect(await screen.findByText("Bred dummy")).toBeInTheDocument();
    expect(screen.getByRole("button")).toHaveAttribute("disabled");
  });

テスト2はエラーが返ってきた時ボタンは活性化されていてエラーメッセージが表示されて名前のタグは生成されていないこと

it("[Fetch failure]Should display error msg, no render heading and button able", async () => {
    server.use(
      rest.get(
        "https://jsonplaceholder.typicode.com/users/1",
        (req, res, ctx) => {
          return res(ctx.status(404));
        }
      )
    );
    render(<MockServer />);
    userEvent.click(screen.getByRole("button"));
    expect(await screen.findByTestId("error")).toHaveTextContent(
      "Fetching Failed!"
    );
    expect(screen.queryByRole("heading")).toBeNull();
    expect(screen.getByRole("button")).not.toHaveAttribute("disabled");
  });

全体像

import React from "react";
import { render, screen, cleanup } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

import { rest } from "msw";
import { setupServer } from "msw/node";
import MockServer from "./MockServer";

// 擬似的なAPIのサーバーを作る
const server = setupServer(
  rest.get("https://jsonplaceholder.typicode.com/users/1", (req, res, ctx) => {
    return res(ctx.status(200), ctx.json({ username: "Bred dummy" }));
  })
);

// 擬似サーバーを起動しておく
beforeAll(() => server.listen());
afterEach(() => {
  server.resetHandlers();
  cleanup();
});
afterAll(() => server.close());

describe("Mocking API", () => {
  it("[Fetch success] Should display fetch data correctly and button disable", async () => {
    render(<MockServer />);
    userEvent.click(screen.getByRole("button"));
    expect(await screen.findByText("Bred dummy")).toBeInTheDocument();
    expect(screen.getByRole("button")).toHaveAttribute("disabled");
  });
  it("[Fetch failure]Should display error msg, no render heading and button able", async () => {
    server.use(
      rest.get(
        "https://jsonplaceholder.typicode.com/users/1",
        (req, res, ctx) => {
          return res(ctx.status(404));
        }
      )
    );
    render(<MockServer />);
    userEvent.click(screen.getByRole("button"));
    expect(await screen.findByTestId("error")).toHaveTextContent(
      "Fetching Failed!"
    );
    expect(screen.queryByRole("heading")).toBeNull();
    expect(screen.getByRole("button")).not.toHaveAttribute("disabled");
  });
});