ReactでUnitテストを行ってみた
はい、hebiです。
ReactでUnitテストを行ってみたいと思います。
以下で作成したコンポーネントを対象にテストを実施しますので、先に見てみてください。
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テストコードを実装する工数を確保し、何度でも繰り返しテストを行える仕組みを作りましょう。
最後までお読みいただきありがとうございました(^^♪