msw
mswはブラウザー、Node.jsで用いられるAPIモックライブラリです。特定のリクエストを傍受し定めた方法で処理できます。
Agnostic、Seamless、Reusableの3つを特徴として掲げています。
- Agnostic
- 環境・フレームワーク・ツールに依存せず利用可能
- Seamless
- ネットワークレベルで傍受するのでアプリケーションレベルの整合性に干渉しない
- Reusable
- 環境・フレームワーク・ツールそれぞれでAPIモックを共有可能
そんなmswですが、2023年10月23日にバージョン2.0.0がリリースされました。バージョン2.0.0はそれ以前のバージョンと比べて記法が大きく変わります。この記事では2.0.0で変更された記法について紹介します(graphql
については詳しくないのでhttp
リクエストについて注目して紹介します)。
Node.jsの18.0.0、TypeScriptの4.7以降の環境でしか動作しないことに注意してください。
setupWorker
これまでブラウザーでmswを有効にするにはmsw
からsetupWorker
を呼び出して以下のように記述していました。
import { setupWorker } from 'msw';
const worker = setupWorker(...handlers);
worker.start();
基本的な利用方法は変わりませんが、バージョン2.0.0ではmsw/browser
からインポートする必要があります。
import { setupWorker } from 'msw/browser';
const worker = setupWorker(...handlers);
worker.start();
リクエストハンドラの定義
setupWorker
に定義したhandlers
は以下のようにrest
を用いたリクエストハンドラーを配列で定義していました。
import { rest, setupWorker } from 'msw';
setupWorker([
rest.get('/events', mockEvents),
]);
これからはhttp
を用いて定義します。
import { graphql, http } from 'msw';
http.get('/events', mockEvents);
HTTPメソッドを取り出す部分や第1引数にパスを渡す部分は変わっていませんが、第2引数に渡すレスポンスリゾルバの記述方法が変更されました。さらに、第3引数としてハンドラオプションを渡せるようになりました。
レスポンスリゾルバ
先ほど記述したmockEvents
のようなレスポンスリゾルバを定義する方法も変更されました。
これまでは引数でreq
とres
、ctx
の3つを受け取ってそれを元に処理を組み立てていました。
import type { ResponseResolver, MockedRequest, restContext } from 'msw';
const mockEvents: ResponseResolver<RestRequest, RestContext> = (
req,
res,
ctx,
) => {
return res(ctx.status(200), ctx.json({}));
};
今後はそれらの引数を用いて処理を定義する方式が廃止されます。上記の例を2.0.0形式で書くと新たなAPIであるHttpResponse
を用いて以下のように記述します。
import { type ResponseResolver, HttpResponse } from 'msw';
const mockEvents: ResponseResolver = () => {
return HttpResponse.json({});
};
以下のようにFetch APIのResponse
を用いることでも記述できます。
import { type ResponseResolver, HttpResponse } from 'msw';
const mockEvents: ResponseResolver = () => {
return new Response(
JSON.stringify({}),
{
headers: {
'Content-Type': 'application/json',
},
},
);
};
HttpResponse
はResponse
に比べてにレスポンスクッキーのモックのような特定な機能を可能にします。公式では一貫性のために特別な理由がない限りHttpResponse
を使用することを推奨しています。
レスポンスリゾルバでは既存の3つの引数が廃止された代わりに、新たに1つのオブジェクト形式の引数を取れるようになりました。
import { type ResponseResolver, HttpResponse } from 'msw';
const mockEvents: ResponseResolver = ({
request,
params,
cookies,
}) => {
return HttpResponse.json({});
};
オブジェクトはrequest
とparams
とcoolies
の3つの値を持ちます。request
はFetch APIのRequest
、params
はRecord<string, string[] | string>
、cookies
はRecord<string, string>
の型を持ちます。これらを用いてより直感的に処理を記述できるようになりました。
import { type ResponseResolver, HttpResponse } from 'msw';
const mockEvents: ResponseResolver = ({
request,
params,
cookies,
}) => {
const url = URL(request.url)
const { id } = params;
const { token } = cookies;
})
passthrough
レスポンスリゾルバで処理後にそのままリクエストを実行させる場合について考えます。これまでは第1引数のreq
を用いて以下のように記述していました。
import type { ResponseResolver, MockedRequest, restContext } from 'msw';
const mockEvents: ResponseResolver<RestRequest, RestContext> = (
req,
) => {
return req.passthrough();
};
2.0.0からはreq
を用いることはできないので、msw
からpasstrough
を新たにインポートして記述します。
import { passthrough, type ResponseResolver } from 'msw';
const mockEvents: ResponseResolver = () => {
return passthrough();
};
deplay
処理の遅延もこれまでは、第3引数のctx
を用いて以下のように行なっていました。
import type { ResponseResolver, MockedRequest, restContext } from 'msw';
const mockEvents: ResponseResolver<RestRequest, RestContext> = (
req,
res,
ctx,
) => {
return res(
ctx.delay(100),
ctx.status(200),
ctx.json({}),
);
};
今後はdelay
をmsw
からインポートして行います。
import { delay, type ResponseResolver, HttpResponse } from 'msw';
const mockEvents: ResponseResolver = () => {
delay(100);
return HttpResponse.json({});
};
bypass
レスポンスリゾルバ内でmswのAPIモックに関わらないネットワーク上でHTTPリクエストを行うことを考えます。
これまではctx
が持つfetch
を元に通信していました。
import type { ResponseResolver, MockedRequest, restContext } from 'msw';
const mockEvents: ResponseResolver<RestRequest, RestContext> = (
req,
res,
ctx,
) => {
const response = await ctx.fetch(req);
const resopnseJson = await response.json();
return res(
ctx.status(200),
ctx.json({
...responseJson,
}),
);
};
今後はmsw
からbypass
をインポートして以下のように書きます。
import { bypass, type ResponseResolver, HttpResponse } from 'msw';
const mockEvents: ResponseResolver = ({
request,
}) => {
const response = await fetch(bypass(request));
const resopnseJson = await response.json();
return HttpResponse.json({
...responseJson,
});
};
once
エラー処理の検証のために特定のレスポンスリゾルバの処理を1回だけ実行したいケースがあります。これまではレスポンスリゾルバ内で第2引数のres
を用いて以下のように書く必要がありました。
import { rest, setupWorker } from 'msw';
import type { ResponseResolver, MockedRequest, restContext } from 'msw';
const mockEvents: ResponseResolver<RestRequest, RestContext> = (
req,
res,
ctx,
) => {
return res.once(
ctx.status(200),
ctx.json({
...responseJson,
}),
);
};
setupWorker([
rest.get('/events', mockEvents),
]);
今後はレスポンスリゾルバではなく、リクエストハンドラの第3引数で指定します(ハンドラのオプションとして渡せるのは現在once
だけです)。
import { type ResponseResolver, HttpResponse } from 'msw';
import { setupWorker } from 'msw/browser';
const mockEvents: ResponseResolver = () => {
return HttpResponse.json({});
};
const worker = setupWorker([
http.get('/events', mockEvents, {
once: true,
}),
]);
これまではハンドラのオプションについてもリゾルバに定義していて再利用が難しかったり、役割におけるコードの分割がうまくできていなかった箇所が解決したのでとても使い心地が良くなりました。
さいごに
msw
のバージョン2.0.0の変更を紹介しました。破壊的変更が多く、バージョンアップには苦労しそうですが、記述方法などは直感的でわかりやすく実現したいことをそのまま表現しやすくなったように感じました。