LoginSignup
4
4

More than 1 year has passed since last update.

【React】Mock Service Workerで模擬したサーバレスポンスをテストに活用する

Last updated at Posted at 2021-06-05

現在、Testing Libraryについて学習中です。
今回はサーバのレスポンスをMock Service Workerで模擬し、Testing Libraryで作成したテストに活用することに挑戦してみました。

Mock Service Workerとは

Mock Service Worker(msw)は、ネットワーク呼び出し(APIリクエスト)をインターセプトして特定のレスポンスを返すことを目的としたライブラリです。
mswを利用することで、サーバとの通信を行わずにレスポンスを利用したテストをできたり、SPA開発時のモックサーバとして活用することができます。

実装

以下のコンポーネントで構成された画面を考えます。
ScoopOptionの画像とアイテム名がサーバから取得するデータです。

また、サーバからデータを取得できなかったとき、Options内で警告バナー(AlertBanner)を表示するようにします。

以下の2パターンを確認するためのテストを作成しました。

  • モックサーバの模擬データを正しく取得できているかどうか
  • サーバからのデータ取得の失敗を模擬して警告が表示されるかどうか

セットアップ

mswのセットアップは以下の流れで行います。

  • ライブラリのインストール
  • handlersの作成
  • test serverの作成
  • setUpTestsの作成(create-react-appの場合)

ライブラリのインストール

以下のコマンドでライブラリをインストールします。

npm install msw

handlersの作成

handlersはモックするリクエストとレスポンスの中身を指定するものです。
REST APIとGraphQlのどちらにも対応しており、今回はREST APIで作成します。

ctxでレスポンスの返し方を決めることができ、例えばctx.jsonとすることでjson形式に変換しています。

handlers.js
// src/mocks/handlers.js
import { rest } from 'msw';

export const handlers = [
  rest.get('http://localhost:3030/scoops', (req, res, ctx) => {
    return res(
      ctx.json([
        { name: 'Chocolate', imagePath: '/images/chocolate.png' },
        { name: 'Vanilla', imagePath: '/images/vanilla.png' },
      ])
    );
  }),
];

test serverの作成

作成したhandlersを使用して、実際のモックサーバ(test server)を作成します。

server.js
// src/mocks/server.js
import { setupServer } from 'msw/node';
import { handlers } from './handlers';

// handlersを元にモックサーバを作成
export const server = setupServer(...handlers);

setUpTestsの作成

create-react-appの場合はsetUpTestsに以下の内容を記述します。
テスト中のモックサーバの設定を行います。

setupTests.js
// src/setupTests.js
import { server } from './mocks/server.js'

// モックサーバのlistenをすべてのテストの前に一回だけ行う
beforeAll(() => server.listen())

// 他のテストに影響を与えないようにテストごとにhandlersをリセットする
afterEach(() => server.resetHandlers())

// すべてのテストが終了したらモックサーバをcloseする
afterAll(() => server.close())

テスト1の作成

モックサーバの模擬データを正しく取得できているかどうかを確認するテストを作成します。

テストするファイルの中身は以下のようになっています。

Options.jsx
import axios from 'axios';
import { useEffect, useState } from 'react';
import Row from 'react-bootstrap/Row';
import ScoopOption from './ScoopOption';
import ToppingOption from './ToppingOption';
import AlertBanner from '../common/AlertBanner';
import { pricePerItem } from '../../constants';

export default function Options({ optionType }) {
  const [items, setItems] = useState([]);
  const [error, setError] = useState(false);

  // optionType is 'scoop' or 'toppings'
  useEffect(() => {
    axios
      .get(`http://localhost:3030/${optionType}`)
      .then((response) => setItems(response.data))
      .catch((error) => setError(true));
  }, [optionType]);

  if (error) {
    return <AlertBanner />;
  }

  // TODO: replace `null` with ToppingOption when available
  const ItemComponent = optionType === 'scoops' ? ScoopOption : ToppingOption;
  const title = optionType[0].toUpperCase() + optionType.slice(1).toLowerCase(); //頭文字だけ大文字

  const optionItems = items.map((item) => (
    <ItemComponent
      key={item.name}
      name={item.name}
      imagePath={item.imagePath}
    />
  ));

  return (
    <>
      <h2>{title}</h2>
      <p>{pricePerItem[optionType]} each</p>
      <Row>{optionItems}</Row>
    </>
  );
}

テストの中身は以下のようになります。

Options.test.jsx
import { render, screen } from '@testing-library/react';
import Options from '../Options';

test('displays image for each scoop option from server', async () => {
  render(<Options optionType="scoops" />);

  // 画像が2個取得できていれば成功
  const scoopImages = await screen.findAllByRole('img', { name: /scoop$/i });
  expect(scoopImages).toHaveLength(2);

  // 画像のaltが設定されていれば成功
  const altText = scoopImages.map((element) => element.alt);
  expect(altText).toEqual(['Chocolate scoop', 'Vanilla scoop']);
});

前項のsetUpTestsの作成でテストを実行したときにモックサーバをlistenするような設定を行いました。
そのため、Optionsコンポーネントではhttp://localhost:3030/${optionType}からサーバのデータを取得しているのですが、テスト実行中はモックサーバからデータを取得するようになっています。

テスト2の作成

サーバからのデータ取得の失敗を模擬して、警告が表示されるかどうかをテストします。

OrderEntry.test.jsx
import { render, screen, waitFor } from '@testing-library/react';
import OrderEntry from '../OrderEntry';
import { rest } from 'msw';
import { server } from '../../../mocks/server';

test('handles error for scoops and toppings routes', async () => {
  // handlersを上書きする
  server.resetHandlers(
    rest.get('http://localhost:3030/scoops', (req, res, ctx) =>
      res(ctx.status(500))
    ),
  );

  render(<OrderEntry />);

  await waitFor(async () => {
    const alerts = await screen.findAllByRole('alert');
    expect(alerts).toHaveLength(1);
  });
});

エラーレスポンス(500)を返すように、server.resetHandlersを使ってhandlersを上書きします。

また、警告アラート(AlertBanner)はデータを取得できなかったとき(axiosがエラーをなげたとき)に非同期で表示されます。
そのようなケースでは、要素の取得にawait find[All]Byを使用します。

しかし、これだけだとWarningがでるので、waitForも併用します。

参考資料

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4