14
3

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 1 year has passed since last update.

mswのバージョン2.0.0で行われた破壊的変更を確認する

Posted at

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のようなレスポンスリゾルバを定義する方法も変更されました。
これまでは引数でreqresctxの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',
      },
    },
  );
};

HttpResponseResponseに比べてにレスポンスクッキーのモックのような特定な機能を可能にします。公式では一貫性のために特別な理由がない限りHttpResponseを使用することを推奨しています。

レスポンスリゾルバでは既存の3つの引数が廃止された代わりに、新たに1つのオブジェクト形式の引数を取れるようになりました。

import { type ResponseResolver, HttpResponse } from 'msw';

const mockEvents: ResponseResolver = ({
  request,
  params,
  cookies,
}) => {
  return HttpResponse.json({});
};

オブジェクトはrequestparamscooliesの3つの値を持ちます。requestはFetch APIのRequestparamsRecord<string, string[] | string>cookiesRecord<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({}),
  );
};

今後はdelaymswからインポートして行います。

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の変更を紹介しました。破壊的変更が多く、バージョンアップには苦労しそうですが、記述方法などは直感的でわかりやすく実現したいことをそのまま表現しやすくなったように感じました。

14
3
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
14
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?