APIを直接呼ぶ場合
例えば、Renderというコンポーネントが初期表示時にUseEffectを使用してAPIで表示するデータを取得する処理があった場合は下記のようなテストを書きます。
1 2 3 4 5 6 7 8 9 10 11 |
import React from "react"; import { render, screen } from "@testing-library/react"; import Render from "./Render"; describe("API", () => { it("APIテスト", async () => { render(<Render />); expect(screen.queryByText(/テスト/)).toBeNull(); //APIの結果が取得されないこと。 expect(await screen.findByText(/テスト/)).toBeInTheDocument(); //APIの結果が取得されること。 }); }); |
awaitをする場合はAPIの実行結果を待つのでデータが表示されることをチェックし、awaitをしない場合はAPIの結果を待たないのでデータが表示されないというようなテストケースを作ることになります。
jest.mock("axios")を使ってモック化する。(最近は非推奨)
axiosをモック化する場合は以下のように記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import {render,screen} from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import axios from "axios"; import {Render} from "./Render" const user = userEvent.setup(); jest.mock("axios"); const mockAxios = jest.mocked(axios); describe("axios mock",()=>{ beforeEach(()=>{ mockAxios.get.mockReset(); }); it("入力フォームに入力した内容でAPIリクエストが送信される",async ()=>{ const userInfo = { id:1, name: "Taro" }; const resp = { data: userInfo }; mockAxios.get.mockResolvedValue(resp); render(<Render />) const input = screen.getByRole("textbox"); await user.type(input,userInfo.name); const button = screen.getByRole("button"); await user.click(button); // コンポーネント側で「/api/users?query="ユーザー名"」というAPIをaxiosで叩いている。 expect(mockAxios.get).toHaveBeenCalledWith(`/api/users?query=${userInfo.name}`) }) }) |
mock service workerを使ってモックを使う場合(最近は推奨)
mock service workerという物を使うことが公式で推奨されています。
axiosのモックの問題点
一言でいえば、axios自体をmock化しているのでaxiosとの依存関係がなくなってしまう。
- axiosをそのままmockしているのでaxiosのモジュール自体にバグが入った場合でも動いてしまう。
- axiosのライブラリ自体が変わってしまい戻り値が変わった場合でも動いてしまう。
mswを使えばaxios自体をモックせず、axiosと通信をmswがインターセプトしてモック値を返します。なので、モック化されていないaxiosと通信することになるので上記の問題を解消できるとのことです。
インストール
1 |
npm install msw --save-dev |
実装
例えば、GETリクエストであれば下記のような感じになります。APIのエンドポイントを指定すれば外部のAPIではなくこちらのモックの内容を返してくれます。下記のセットアップをすれば、テスト対象のコンポーネント内でAPIエンドポイントにアクセスすればモックサーバーが結果を返してくれます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import { render, screen, cleanup } from "@testing-library/react"; import { rest } from "msw"; import { setupServer } from "msw/node"; const server = setupServer( rest.get("APIのエンドポイントのURL", (req, res, ctx) => { return res(ctx.status(200), ctx.json({ name:"太郎" })); }) ); beforeAll(() => server.listen()); afterEach(() => { server.resetHandlers(); cleanup(); }); afterAll(() => server.close()); |
リクエストのことで、リクエストパラメータを追加したい場合とかに使ったりします。
res
上の例で言えばGETに対するレスポンスのことです。
ctx
コンテキストのことです。jsonなどレスポンスのオブジェクトのダミーの内容を定義できます。
beforeAll(() => server.listen());
全てのテストコード実行前にモックサーバーを起動します。
server.resetHandlers();
サーバーを一旦リセットします。各テストコードが終わる度にこれを呼ぶ決まりになっているので、afterEachと一緒に記述することが定例になっています。各テストケース間で副作用が起こらないようにするための記述です。
afterAll(() => server.close());
全てのテストコード実行後にモックサーバーを終了します。各テストケース間で副作用が起こらないようにするための記述です。
複数のAPIエンドポイントを定義したい場合
以下のようにsetupServerに対して
1 2 3 4 5 6 7 8 9 10 |
const handlers = [ rest.post('エンドポイントURL1', (req, res, ctx) => { return res(ctx.status(200), ctx.json({ token: 'abc123' })); }), rest.post('エンドポイントURL2', (req, res, ctx) => { return res(ctx.status(201)); }), ]; const server = setupServer(...handlers); |
あるテストケースだけエンドポイントのレスポンス結果を変えたい場合
1 2 3 4 5 6 7 8 9 10 11 |
it('あるエンドポイントだけ変える', async () => { server.use( rest.post('http://localhost:8000/api/auth/', (req, res, ctx) => { return res(ctx.status(400)); }) ); render( <xxx/> ); 処理 }); |
itの中で、server.useを使います。そうすることで特定のit内でのみ特定のエンドポイントの返す結果を変えることが可能です。
用途
あるテストケースだけ意図的に処理を失敗させたい場合などに使えたりします。
この記事へのコメントはありません。