プログラミング

ReactでUnitテストを行ってみた

hebishima.shogo

はい、hebiです。

ReactでUnitテストを行ってみたいと思います。

以下で作成したコンポーネントを対象にテストを実施しますので、先に見てみてください。

あわせて読みたい
ReactでDIする
ReactでDIする
スポンサーリンク

Login.test.tsxを作成

テスト用のファイル「Login.test.tsx」を作成します。Login.test.tsxのようにファイル名に「test」を記述します。

モック化

Unitテストを行うためには依存モジュールをモック化する必要があります。
今回はログインコンポーネントが使用しているuseRepository関数とLoginRepositoryクラスをモック化していきます。

関数のモック化

モック化対象のコンポーネントを指定し、useRepository関数をモック化します。

// mock化するコンポーネントを指定
jest.mock("../providers/RepositoryProvider");

// useRepository関数をモック化
const useRepositoryMock = useRepository as jest.MockedFunction<
  typeof useRepository
>;

クラスをモック化

useRepository関数の戻り値となるLoginReopsitoryクラスをモック化します。(mockLoginRepository関数を作成)
mockLoginRepository関数の引数には、login関数の戻り値と、成功/失敗パターンのテストを指定できるようにしてみました。
isErrorにfalseを指定すると、statusに200を返し、dataにユーザ情報を返します。(呼び出し元のthenにデータが返ります)
isErrorにtrueを指定すると、statusに400を返します。(呼び出し元はcatchにデータが返ります)

// LoginRepositoryをMock化
const mockLoginRepository = (user: User, isError: boolean = false) => {
  // 成功時のテストデータを返す
  let promise = Promise.resolve({ status: 200, data: user });
  if (isError) {
    // 失敗時のテストデータを返す
    promise = Promise.reject({ status: 400 });
  }
  const loginMock = jest.fn().mockReturnValue(promise);

  // ILoginRepositoryを実装したmock関数を作成する
  const mockRepository = jest
    .fn<ILoginRepository, []>()
    .mockImplementation(() => ({
      login: loginMock,
    }));

  return {
    mockRepository,
    loginMock,
  };
};

モック関数の戻り値を設定

作成したモッククラスをuseRepository関数の戻り値に設定します。

// モックログインリポジトリクラス取得
const loginRepository = mockLoginRepository(user);

// useRepositoryMockの戻り値を設定(レンダリングする前に戻り値を設定する必要がある)
useRepositoryMock.mockReturnValue(
  new RepositoryFactory(
    loginRepository.mockRepository(),
    new LogoutRepository()
  )
);

テスト対象コンポーネントのレンダリング

React Testing Libraryの機能を利用して、テスト対象コンポーネントをレンダリングします。
レンダリングすることで、ボタン等の要素を操作することができます。

// ログインコンポーネントをレンダリング
render(<Login />);

ボタンをクリック

ログインボタンの要素を取得してクリックを実行します。

// ログインボタンを取得
const button = screen.getByRole("button", { name: /login/i });
// クリック
fireEvent.click(button);

テストの結果確認

今回はログインが実行されたかをテストの確認とします。

// loginが実行されたかを判定
expect(loginRepository.loginMock).toBeCalled();

// 以下のように呼び出し回数も確認できる
// expect(loginRepository.loginMock.mock.calls.length).toBe(1);

テスト実行

「npm test」を実行すると、テストが実行されます。

全量

Unitテストコードの全量です。LogoutRepositoryクラスも同様にモック化してテストを実施してます。

import React from "react";
import { render, cleanup, screen, fireEvent } from "@testing-library/react";
import { Login } from "./Login";
import { ILoginRepository } from "../repositorys/ILoginRepository";
import { ILogoutRepository } from "../repositorys/ILogoutRepository";
import { RepositoryFactory } from "../repositorys/RepositoryFactory";
import { useRepository } from "../providers/RepositoryProvider";
import { LogoutRepository } from "../repositorys/LogoutRepository";
import { LoginRepository, User } from "../repositorys/LoginRepository";
// mock化するコンポーネントを指定
jest.mock("../providers/RepositoryProvider");

// useRepository関数をモック化
const useRepositoryMock = useRepository as jest.MockedFunction<
  typeof useRepository
>;

afterEach(cleanup);

// LoginRepositoryをMock化
const mockLoginRepository = (user: User, isError: boolean = false) => {
  // 成功時のテストデータを返す
  let promise = Promise.resolve({ status: 200, data: user });
  if (isError) {
    // 失敗時のテストデータを返す
    promise = Promise.reject({ status: 400 });
  }
  const loginMock = jest.fn().mockReturnValue(promise);

  // ILoginRepositoryを実装したmock関数を作成する
  const mockRepository = jest
    .fn<ILoginRepository, []>()
    .mockImplementation(() => ({
      login: loginMock,
    }));

  return {
    mockRepository,
    loginMock,
  };
};

// LogoutRepositoryをMock化
const mockLogoutRepository = () => {
  const logoutMock = jest
    .fn()
    .mockReturnValue(Promise.resolve({ status: 200 }));

  // ILogoutRepositoryを実装したmock関数を作成する
  const mockRepository = jest
    .fn<ILogoutRepository, []>()
    .mockImplementation(() => ({
      logout: logoutMock,
    }));

  return {
    mockRepository,
    logoutMock,
  };
};

describe("Login Test", () => {
  // ログイン成功テスト
  test("Success", () => {
    // ログインリポジトリをモック化 (name: "hebi"を戻り値として返すようにする)
    const user: User = {
      name: "hebi",
    };

    // モックログインリポジトリクラス取得
    const loginRepository = mockLoginRepository(user);

    // useRepositoryMockの戻り値を設定(レンダリングする前に戻り値を設定する必要がある)
    useRepositoryMock.mockReturnValue(
      new RepositoryFactory(
        loginRepository.mockRepository(),
        new LogoutRepository()
      )
    );

    // ログインコンポーネントをレンダリング
    render(<Login />);

    // screen.debugでレンダリング情報をコンソールに表示できる
    screen.debug();

    // ログインボタンを取得
    const button = screen.getByRole("button", { name: /login/i });
    // クリック
    fireEvent.click(button);

    // loginが実行されたかを判定
    expect(loginRepository.loginMock).toBeCalled();

    // 以下のように呼び出し回数も確認できる
    // expect(loginRepository.loginMock.mock.calls.length).toBe(1);
  });

  // ログインエラーテスト
  test("Error", () => {
    // ログインリポジトリをモック化 (name: "hebi"を戻り値として返すようにする)
    const user: User = {
      name: "hebi",
    };
    const loginRepository = mockLoginRepository(user, true);

    // useRepositoryMockの戻り値を設定(レンダリングする前に戻り値を設定する必要がある)
    useRepositoryMock.mockReturnValue(
      new RepositoryFactory(
        loginRepository.mockRepository(),
        new LogoutRepository()
      )
    );

    // ログインコンポーネントをレンダリング
    render(<Login />);

    // screen.debugでレンダリング情報をコンソールに表示できる
    screen.debug();

    // ログインボタンを取得
    const button = screen.getByRole("button", { name: /login/i });
    // クリック
    fireEvent.click(button);

    // loginが実行されたかを判定
    expect(loginRepository.loginMock).toBeCalled();

    // 以下のように呼び出し回数も確認できる
    // expect(loginRepository.loginMock.mock.calls.length).toBe(1);
  });
});

// ログアウトテスト
test("loout Test", () => {
  // ログアウトリポジトリをモック化
  const logoutRepository = mockLogoutRepository();
  // useRepositoryMockの戻り値を設定(レンダリングする前に戻り値を設定する必要がある)
  useRepositoryMock.mockReturnValue(
    new RepositoryFactory(
      new LoginRepository(),
      logoutRepository.mockRepository()
    )
  );

  // ログインコンポーネントをレンダリング
  render(<Login />);

  // ログアウトボタンを取得
  const button = screen.getByRole("button", { name: /logout/i });
  // クリック
  fireEvent.click(button);

  // logoutが実行されたかを判定
  expect(logoutRepository.logoutMock).toBeCalled();
});

最後に

importしたコンポーネントを簡単にモック化できるので、Unitテストも比較的簡単に実施できました。
Unitテストコードを実装する工数を確保し、何度でも繰り返しテストを行える仕組みを作りましょう。

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

スポンサーリンク
ABOUT ME
hebi
hebi
エンジニア
フルスタックエンジニアとして活躍中。
HTML5プロフェッショナル認定Level1、Level2所持者です。

未経験の方でも簡単にプログラミングを学べるようにと情報を発信しております。
記事URLをコピーしました