6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

MSW (Mock Server Worker)を使ったAPI周りのテストメモ

Posted at

概要

入社前の研修として作成していたTodoアプリのテスト実装時に、API周りのテスト実装で詰まってしまったので、備忘録としてこの記事を書いています。

API周りのテストについて

API周りのテストを行う方法としては、以下のようなものがあります。

各テストごとにfetchそのものをモックする

自分が最初に実践したのはこの方法です。
以下の記事を参考に実装を行いました。

非同期リクエストを扱うコンポーネントのテスト:fetch そのものをモック、実装編

モックを作成しておき...

const loginMock = () =>
  new Promise((resolve) => {
    resolve({
      ok: true,
      status: 200,
      json: async () => ({ token: mockToken }),
    });
  });

テストごとに応じて、fetchにモックを適用させる。

describe('ログインテスト', () => {
  global.fetch = jest.fn().mockImplementation(loginMock);
  //...
}

この方法は、実装が非常に簡単でした。
しかし...

  • 各テストごとにいちいちモックを適用しなくてはならない
  • 各テストごとに1つしかAPI処理をモック出来ないため、柔軟性が低い (これがキツイ...)

こういった点から、この方法はボツに。

ApolloやMSWを使ってモックサーバーを作成する

モックサーバーを作成する方法はいくつかありましたが、
今回は手軽さを考えて、MSWを使用しました。
(GraphQLについての知識もなかったので…👼)
実装方法について、次項から記述していきます。

モックサーバーを作成していく

いい感じに例がありましたので、こちらのリポジトリを参考に実装していきます。
mswjs examples (github)
この記事では、ユーザー登録・ログインについてモックを作成していきます。

MSWを導入

  • npm
npm install -D msw
  • yarn
yarn add msw --dev

ファイル構成

src
 | --- mocks
 |      | --- server.ts
 |      | --- handlers
 |               | --- handlers.ts
 |               | --- auth.ts
 |
 | --- __tests__ 
 |
 | --- jest.config.js
 | --- jest.setup.js
 .
 .
 .

jest.setup.js

import 'whatwg-fetch'; //テスト環境ではfetchがないため、別ライブラリから用意

import { server } from './mocks/server';

beforeAll(() => {
  server.listen(); //モックサーバーの起動
});

afterEach(() => {
  server.resetHandlers();
});

afterAll(() => {
  server.close(); //モックサーバーの停止
});

jest.config.js

module.exports = {
//テスト実行時にjest.setup.jsの処理を実行する
  setupFilesAfterEnv: ['./jest.setup.js'],
};

server.ts

import { setupServer, SetupServerApi } from 'msw/node';
import { handlers } from './handlers/handlers';

export * from 'msw';

export const server: SetupServerApi = setupServer(...handlers);

handlers.ts (各モックのハンドラーをここでまとめてexport)

import { handlers as authHandlers } from './auth';

export const handlers = [...authHandlers];

auth.ts

import { rest } from 'msw';

interface AuthRequestBody {
  email: string;
  password: string;
}

export let users = [
  {
    id: '1',
    email: 'test@test.com',
    password: 'testtest',
  },
];

export const mockToken = 'mocktoken-test';

const loginHandler = rest.post<AuthRequestBody>(
  `${process.env.NEXT_PUBLIC_API_URL}/users/login`,
  (req, res, ctx) => {
    const { email, password } = req.body;

    if (email === '' || password === '') {
      return res(ctx.status(500));
    }

    const match = users.filter((user) => {
      return user.email === email && user.password === password;
    });

    if (false) {
      return res(ctx.status(401));
    } else {
      return res(
        ctx.status(200),
        ctx.json({
          token: mockToken,
        })
      );
    }
  }
);

const registerHandler = rest.post<AuthRequestBody>(
  `${process.env.NEXT_PUBLIC_API_URL}/users`,
  (req, res, ctx) => {
    const { email, password } = req.body;

    if (email === '' || password === '') {
      return res(ctx.status(500));
    }

    const match = users.filter((user) => {
      return user.email === email;
    });

    if (match.length !== 0) {
      return res(ctx.status(409));
    } else {
      return res(
        ctx.status(200),
        ctx.json({
          email: email,
        })
      );
    }
  }
);

export const handlers = [loginHandler, registerHandler];

このようにコードを書き、テストを実行するとモックサーバーが起動します。
後は環境に合わせてテストコードを書いていきましょう。

例えば、自分の場合は以下のようなコードを書きました。

import React from 'react';
import Login from '../pages/login';
import { render, cleanup } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

jest.mock('next/router', () => ({
  useRouter() {
    return {
      push: jest.fn(String),
    };
  },
}));

afterEach(cleanup);


describe('ログインテスト', () => {

  it('inputの入力テスト', () => {
    const { getByPlaceholderText } = render(<Login />);
    const emailInput = getByPlaceholderText('email') as HTMLInputElement;
    const passwordInput = getByPlaceholderText('password') as HTMLInputElement;
    userEvent.type(emailInput, 'test@test.com');
    userEvent.type(passwordInput, 'testtest');
    expect(emailInput.value).toBe('test@test.com');
    expect(passwordInput.value).toBe('testtest');
  });

  it('ログイン成功時のテスト', async () => {
    const { getByPlaceholderText, getByText, findByText } = render(<Login />);
    const emailInput = getByPlaceholderText('email') as HTMLInputElement;
    const passwordInput = getByPlaceholderText('password') as HTMLInputElement;
    const loginButton = getByText('Login');
    userEvent.type(emailInput, 'test@test.com');
    userEvent.type(passwordInput, 'testtest');
    userEvent.click(loginButton);
    expect(findByText('All')).toBeTruthy();
  });
});

MSWを利用して思ったこと

  • 思った以上に実装が手軽に行えた。
  • 学習コストもあまり高くない。
  • Responseのステータスも柔軟に設定可能。

おわりに

MSWの利用方法について備忘録を書きました。
簡単に扱えたので、今後もお世話になりそうです。
今後の状況次第だけど、Apolloも試してみたいな...

参考にさせて頂いたサイト

6
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
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?