mizdra's blog

ぽよぐらみんぐ

msw で handler の mock や spy をする

元々「ServiceWorker をベースにした技術をわざわざテストに持ち込む意味とは?」と思って、msw をテスト環境で使う意義について懐疑的だったのですが、いざ使ってみるとすごく便利ですね。ServiceWorker 云々以前に、ネットワークリクエストの mock ライブラリとして、インターフェイスがとても使いやすいです。

zenn.dev

特に handler の mock や spy が簡単にできるのが嬉しいです。Jest と組み合わせた時の例だと、以下のようなイメージ。

// jest.setup.ts
import { server } from './src/test-utils/msw';

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// src/test-utils/msw.ts
import { setupServer } from 'msw/node';
import { graphql, rest } from 'msw';

export type TodoAddRequest = { title: string };
export type TodoAddResponse =
  { type: 'success' }
  | { type: 'error', error: string };

const handlers = [
  // デフォルトの handler をここで定義しておく。
  rest.post<TodoAddRequest>('/todo/add', (req, res, ctx) => {
    return res(
      ctx.json<TodoAddResponse>({ type: 'success' }),
    );
  }),
  // ...
];

export const server = setupServer(...handlers);
// src/components/TodoList.test.ts

import { server, TodoAddResponse } from '../test-utils/msw';

// mock の例

test('todo 追加時にエラーになったら、エラーメッセージが表示される', async () => {
  server.use(rest.post('/todo/add', (req, res, ctx) => {
    // 既に `/todo/add` 向けに登録されている handler を無視して、
    // この handler で上書きする (mock)
    return res(
      ctx.status(500),
      ctx.json<TodoAddResponse>({
        type: 'error',
        error: 'TODO は 10 個までしか登録できません。',
      }),
    );
  }));

  render(<TodoList />);
  
  await userEvent.type(screen.getByLabelText('title'), '買い物に行く');
  await userEvent.click(screen.getByRole('button', { name: '追加' }));

  expect(screen.getByRole('alert')).toHaveTextContent(
    'TODO は 10 個までしか登録できません。',
  );
});

// spy の例

test('todo を追加できる', async () => {
  const todoAddSpy = jest.fn();
  // これを `server.use` に渡す。 `return res(...)` していないので、
  // デフォルトの handler まで貫通する
  server.use(rest.post('/todo/add', todoAddSpy));

  render(<TodoList />);
  
  await userEvent.type(screen.getByLabelText('title'), '買い物に行く');

  expect(todoAddSpy).not.toBeCalled();
  await userEvent.click(screen.getByRole('button', { name: '追加' }));

  // handler が期待通りの呼ばれ方をしたかをチェック
  expect(todoAddSpy).toBeCalled();
  expect(todoAddSpy).toBeCalledWith(expect.objectContaining(
    body: {
      title: '買い物に行く',
    },
  ));
}

テストケース側で server.usereturn res(...) を含む handler を登録すれば mock が、return res(...) を含まない handler を登録すれば spy ができます*1。便利ですね。

*1:よく考えてみたら、デフォルトの handler の入力を傍受できるだけで出力は傍受できないので、spy と言えるかどうか微妙な気がしてきた...

ポケットモンスター・ポケモン・Pokémon・は任天堂・クリーチャーズ・ゲームフリークの登録商標です.

当ブログは @mizdra 個人により運営されており, 株式会社ポケモン及びその関連会社とは一切関係ありません.