MSW 2.0 に更新した話

:::note info
この記事は、[LITALICO Engineers Advent Calender 2023](https://qiita.com/advent-calendar/2023/litalico) シリーズ2の9日目の記事です。ただし、12月23日に投稿しています。
:::

# はじめに

LITALICO プロダクトエンジニアリング（PE）部の片桐英人（かたぎり えいと）です。

最近、仕事ナビのフロントエンドのテストや Storybook で使用している  Mock Service Worker（msw）を 1.x から 2.0 に更新しました。仕事ナビのメンバーにそのことを伝えるために書いています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3626295/b7fa4105-7009-14ab-f895-742027b837ab.png)

この記事を記載している時点で、最新は、2.0.11 になっていますね…

# Mock Service Worker (msw) とは？

Mock Service Worker（msw）[^1] は、Service Worker [^2] を使って、HTTP リクエストとそのレスポンスをモックするための JavaScript のライブラリ（npm パッケージ）です。

[^1]: https://mswjs.io/
[^2]: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker

# MSW 2.0

MSW 2.0 は、2023年10月末にリリースされました [^3]。このリリースで、モックの定義方法など多くの破壊的な変更がありました。今回の更新で変更した変更点は、以下になります。記載していない変更方法については、[1.x → 2.x](https://mswjs.io/docs/migrations/1.x-to-2.x/) を参照してください。

[^3]: https://mswjs.io/blog/introducing-msw-2.0/

## 変更内容

### import 内容の変更

MSW 1.x では、REST API のモックの定義に `rest` を使用していましたが、 MSW 2.0 では、`http` に変更されました。また、レスポンスを作成するための `HttpResponse` というクラスが必要になります。

#### 変更前

```javascript
import { rest } from 'msw';
```

#### 変更後

```javascript
import { http, HttpResponse } from 'msw';
```

### handler の定義の変更

MSW 1.x から 2.0 では、REST API のモックする handler の定義方法が大きく変わりました。変更されて、簡単に定義できるようになっています。

#### 変更前

```javascript
(req, res, ctx) => {
  return res(ctx.json({ userId: 1 });
}
```

#### 変更後

```javascript
() => {
  return HttpResponse.json({ userId: 1 });
}
```

### handler 内でクエリパラメータの取得

MSW 2.0 では、クエリパラメータを取得する場合は、handler のパラメータの `info` オブジェクトから `params` 要素を展開して取得するようになっています。

#### 変更前

```javascript
(req, res, ctx) => {
  const { user_id } = req.params;
  ...
}
```

#### 変更後

```javascript
({ params }) => {
  const { user_id } = params;
  ...
}
```

### handler 内でリクエストボディの取得

MSW 2.0 では、リクエストボディを取得する場合は、handler のパラメータの `info` オブジェクトから `request` 要素を展開して取得するようになっています。

#### 変更前

```javascript
async (req, res, ctx) => {
  const body = await req.json();
  ...
}
```

#### 変更後

```javascript
async ({ request }) => {
  const body = await request.json();
  ...
}
```

### レスポンスの HTTP ステータスの変更

レスポンスの HTTP ステータスを `200` 以外にしたい場合は、[`HttpResponse`](https://mswjs.io/docs/api/http-response/) の第2引数のオブジェクト内の `status` で指定します。

#### 変更前

```javascript
async (req, res, ctx) => {
  return res(ctx.status(500));
}
```

#### 変更後

```javascript
async () => {
  return new HttpResponse('Internal Server Error', { status: 500 });
}
```

# 更新時につまずいた点

更新中に色々なエラーが発生しました。いくつかは、「[1.x から 2.x への移行ガイド](https://mswjs.io/docs/migrations/1.x-to-2.x)」の "Frequent issues" に記載されているようにすれば、解決できました。

自分で試行錯誤して解決した点を説明します。

## undici

移行ガイドで、[ポリフィル](https://developer.mozilla.org/ja/docs/Glossary/Polyfill)を追加する際、"Make sure to install `undici`." なので、[undici](https://www.npmjs.com/package/undici) を追加する必要があります。今回の更新作業で msw 2.0.9 にする場合は、undici のバージョンが 5.28.2 にする必要がありました。undici の最新のバージョンは、6.2.1 なので、インストール時に注意が必要です。

```
$ yarn add -D undici@5.28.2
```


この時、[`undici`](https://www.npmjs.com/package/undici) というパッケージを追加する必要がありました。バージョンを 5.28.2 に固定する必要がありました。

```
$ yarn add -D undici@5.28.2
```

## モックへのリクエストが止まる

[jest](https://www.npmjs.com/package/jest) を使ったテスト内で、MSW を使って API リクエストをモックしている箇所で、MSW 2.0 へ更新した後、止まってしまうようになってしまいました。

日時を固定するために以下のようにしている部分がありました。

```
jest.useFakeTimers({
   now: new Date('2023-12-09'),
});
```

ここの箇所をコメントアウトして、テストを実行すると失敗しますが成功するので、この辺りの部分が問題だろうということがわかりました。最終的には、

```
jest.useFakeTimers({
    doNotFake: ['queueMicrotask'],
    now: new Date('2023-12-09'),
});
```

とすることでテストの実行が止まることがなくなりました。理由が良くわかっていないので、機会がある時に調査したいと思います。

## TypeError: Invalid base URL

MSW 2.0 に更新後、実行すると "TypeError: Invalid base URL:" というエラーが発生するテストがいくつかありました。バックトレースが以下のようになっていました。

```
TypeError: Invalid base URL:
  at new URLImpl (/.../node_modules/whatwg-url/lib/URL-impl.js:15:15)                           
  at Object.exports.setup (/.../node_modules/whatwg-url/lib/URL.js:54:12)                       
  at new URL (/.../node_modules/whatwg-url/lib/URL.js:115:22)
  at toAbsoluteUrl (/.../node_modules/@mswjs/interceptors/lib/node/chunk-5OKLCEIP.js:631:10)
```

このバックトレースを参考に辿っていくと`@mswjs/interceptors/lib/node/chunk-5OKLCEIP.js` の631行めの以下の箇所で `location.href` が空文字のためにエラーが発生していることがわかりました。

```javascript
function toAbsoluteUrl(url) {
  if (typeof location === "undefined") {
    return new URL(url);
  }
  return new URL(url.toString(), location.href); // ここ
}
```

テストのコードを見直してみると以下のように `location` オブジェクトをモックしていました。

```typescript
Object.defineProperty(window, 'location', {
  writable: true,
  value: {
     host: 'example.com',
     hostname: 'example.com',
     href: '',
     port: '',
     protocol: 'https:',
  },
});
```

この部分の `href` の箇所を `https://localhost:3000/` などのように URL として正しい文字列を入れることで、更新前のようにテストが成功するようになりました。

ここでは、明示的に `href` を空にしていましたが、`location` オブジェクトをモックしている場合は、同様に `href` に正しい URL 文字列を入れる必要がありました。

# さいごに

MSW 2.0 への更新には、 MSW の API の変更があり、多くの書き換えが必要あり、大変でしたが、新しい API の方が、書きやすくなり、MSW を使ったテストを書く効率が少し上がるのかなぁと思っています。これからも、本番だけでなく、開発やテスト使用するライブラリも積極的に更新していき、良いサービスの迅速に提供できる開発やテスト環境を整備していきたいと思います。