🎪

jest における MSW の活用事例

2022/01/13に公開

MSW を使った jest のテストについて、引数などの検査が面倒という記事を拝見したので、もし同様にアプローチを模索されている方がいらっしゃれば参考に、と思い記事にしました。筆者は普段以下の様に、server.use内にjest.fnを仕込みテストしています。例えば API が2回呼ばれたことを検証する場合、次の様になります。

const server = setupMockServer(...handlers);

describe("API call の検証", () => {
  const mockFn = jest.fn();
  beforeEach(() => {
    server.use(
      rest.get("/path/to/api", async (req, res, ctx) => {
        mockFn(); // <- here
        return res(ctx.json({}));
      })
    );
  });
  test("ボタンを押下すると、API が呼ばれる", async () => {
    render(<MyComponent />);
    const button = await screen.findByRole("button");
    userEvent.click(button);
    userEvent.click(button);
    await waitFor(() => expect(mockFn).toHaveBeenCalledTimes(2));
  });
});

このアプローチを利用すれば、API がどの様に呼び出されたのか、内訳を詳細に調べることができます。MSW のハンドラー内で検証したい対象を抽出し、mockFnの引数として渡します。これで axios の mock と同等の検証が出来ます。

const server = setupMockServer(...handlers);

describe("API call の検証", () => {
  const mockFn = jest.fn();
  beforeEach(() => {
    server.use(
      rest.get("/path/to/api", async (req, res, ctx) => {
        const target = req.url.searchParams.get("search")
        mockFn(target); // <- here
        return res(ctx.json({}));
      })
    );
  });
  test("「test」と入力しエンターキーを押すと、?search=test 付きで API が呼ばれる", async () => {
    render(<MyComponent />);
    const searchbox = await screen.findByRole("searchbox");
    const text = "test";
    userEvent.type(searchbox, `${text}{enter}`);
    await waitFor(() => expect(mockFn).toBeCalledWith(text));
  });
});

この方法は axios の mock よりも手間かもしれませんが、リクエスト詳細を検証するためには、都合が良いこともあります。例えば「リクエスト header に任意の値が挿入されていること」というケースです。req.headers.getで、検査対象の値を抽出します。

server.use(
  rest.get("/path/to/api", async (req, res, ctx) => {
    const target = req.headers.get("x-msw-request-id")
    mockFn(target);
    return res(ctx.json({}));
  })
);

axios の mock を使っても同等の検証は可能ですが、Native Fetch API でも同じテストコードで検証できるため「ライブラリ毎にテストコードが違う」という事になりません。Jest は E2E テストフレームより低コストですから、CI の実行時間(待ち時間)を短縮することができます。MSW と併用し、テストケースの範囲を広げてみてはいかがでしょうか。

Discussion