はじめに
この記事では、Mock Service Worker というAPIをモックするライブラリを使って、フロントエンド自動テストのREST APIコールをモックする手順について記載していきます。
前提
開発環境は以下の通りです。
- Windows11
- VSCode
- TypeScript 5.0.2
- Node.js 18.15.5
- Express 4.18.2
- React 18.2.0
- Vite 4.2.0
- Vitest 0.29.8
- @testing-library/react 14.0.0
事前にテスト対象となるToDoアプリを作成しています。
概要は以下の通りです。
Backend
Backend側では、ToDoリストを持っています。
import express, { Express, Request, Response } from "express";
import { Todo } from "../types";
import cors from "cors";
const app: Express = express();
const port = 3001;
app.use(cors());
const sampleData = [
{ id: 1, title: "牛乳を買う", completed: false },
{ id: 2, title: "洗濯をする", completed: true },
{ id: 3, title: "請求書を支払う", completed: false },
];
let todoList: Todo[] = sampleData;
app.get("/todo-list", (req: Request, res: Response) => {
res.json(todoList);
});
app.listen(port, () => {
console.log(`listening on port ${port}`);
});
Frontend
Frontend側で上記のToDoリストを取得します。
frontend/src/api/index.ts
でAPIをコールするフックを定義します。
import useSWR from "swr";
import { Todo } from "../../../types/";
export const fetcher = async (path: string) => {
try {
const res = await fetch("http://localhost:3001" + path);
return res.json();
} catch (error) {
console.error(error);
}
};
export const useTodoList = () => useSWR<Todo[]>("/todo-list", fetcher);
そして、frontend/src/routes/Home.tsx
で上記のフックを呼び出し、取得したToDoリストを表示します。
import { FC } from "react";
import { useTodoList } from "../api";
export const Home: FC = () => {
const { data: todoList, error, isLoading } = useTodoList();
if (error) return <div>failed to load</div>;
if (isLoading) return <div>loading...</div>;
return (
<div>
<h1>ToDoリスト</h1>
<ul>
{todoList?.map((todo) => (
<li key={todo.id}>
<input
type="checkbox"
id={String(todo.id)}
checked={todo.completed}
/>
<label htmlFor={String(todo.id)}>{todo.title}</label>
</li>
))}
</ul>
</div>
);
};
Frontendの自動テスト
上記のFrontendの自動テストは以下のように実装しています。
この時点では、Home
コンポーネントをレンダリングし、見出しが表示されていることをテストしています。
import { render, screen, waitFor } from "@testing-library/react";
import { Home } from "../../../routes/Home";
describe("Home Page test", () => {
test("Render Home", async () => {
render(<Home />);
await waitFor(() => {
screen.getByRole("heading", { name: "ToDoリスト" });
screen.debug();
});
});
});
しかし、このテストを実行すると、テスト結果は成功となるものの、BackendのREST APIが動作しないため、フェッチエラーが発生してしまいます。
そのため、Mock Service Worker でREST APIのモックを作成します。
1. Mock Service Worker インストール
以下のコマンドでMock Service Worker をインストールします。
yarn add msw --dev
2. モックAPIの定義
まずはモックAPI定義用のファイル src/__tests__/libs/mocks/handlers.ts
を作成します。
次に msw
(Mock Service Worker)から rest
をインポートし、モックを定義します。
import { rest } from "msw";
export const DUMMY_TODO_LIST = [
{ id: 1, title: "牛乳を買う", completed: false },
{ id: 2, title: "洗濯をする", completed: true },
{ id: 3, title: "請求書を支払う", completed: false },
];
export const handlers = [
rest.get("http://localhost:3001/todo-list", (req, res, ctx) => {
return res(ctx.status(200), ctx.json(DUMMY_TODO_LIST));
}),
];
"http://localhost:3001/todo-list"
リクエストに対し、ToDoリストのデータを返すようにしています。レスポンスの引数はそれぞれ以下の通りです。
-
req
:リクエストに関する情報(今回は利用していません) -
res
:モックレスポンスを作成するユーティリティ -
ctx
:モックレスポンス内のステータスコードやヘッダー、ボディなどを設定する機能群
これでモックの定義はできました。
3. 利用設定
先ほど定義したモックAPIを自動テストで利用できるようにします。
まずは、モックサーバー設定用のファイル src/__tests__/mocks/server.ts
を作成します。このファイル内でサーバーインスタンスを作成し、先ほど定義したモックを読み込みます。
import { setupServer } from "msw/node";
import { handlers } from "./handler";
export const server = setupServer(...handlers);
次にモックサーバー設定用のファイルを自動テスト設定用ファイルで読み込みます。
import matchers from "@testing-library/jest-dom/matchers";
import { cleanup } from "@testing-library/react";
import { afterEach, expect } from "vitest";
import { server } from "./mocks/server";
// extends Vitest's expect method with methods from react-testing-library
expect.extend(matchers);
// establish API mocking before all tests
beforeAll(() => server.listen());
afterEach(() => {
// reset any request handlers that we may add during the tests,
// so they don't affect other tests
server.resetHandlers();
// runs a cleanup after each test case (e.g. clearing jsdom)
cleanup();
});
// clean up after the tests are finished
afterAll(() => server.close());
4. テスト実行
これでテストを実行しても先ほどのフェッチエラーは発生しなくなりました。
また、APIレスポンスをモックしたので、レスポンス内容のテストも追加できます。
import "@testing-library/jest-dom";
import { render, screen, waitFor } from "@testing-library/react";
import { Home } from "../../../routes/Home";
import { DUMMY_TODO_LIST } from "../../libs/dummyData";
describe("Home Page test", () => {
test("Render Home", async () => {
render(<Home />);
await waitFor(() => {
screen.getByRole("heading", { name: "ToDoリスト" });
const todoList = screen.getAllByRole("listitem");
expect(todoList).toHaveLength(DUMMY_TODO_LIST.length);
todoList.forEach((todo, index) => {
const DUMMY_TODO = DUMMY_TODO_LIST[index];
expect(todo).toHaveTextContent(DUMMY_TODO.title);
});
screen.debug();
});
});
});
最後に
今回は Mock Service Worker を使って、フロントエンドテストのREST APIコールをモックする手順について記載しました。Mocke Service Worker は他にもたくさん機能があるので、今後扱っていければと思っています。