概要
入社前の研修として作成していた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も試してみたいな...