テスト用のmockデータをフロントで手軽に作成

# はじめに
この記事はHRBrain Advent Calendar 2022カレンダー2の12日目の記事です。

https://qiita.com/advent-calendar/2022/hrbrain

フロントエンドエンジニアの川口です。

本記事ではJestでのユニットテストやStorybookでのStory作成時に必要となるmockデータを、フロントのみで手軽に作成する方法を紹介します。

# mockデータの作成手法
まずmockデータの作成手法としては、ざっくり以下の2つの手法が考えられるかと思います。

### OpenAPI等のスキーマ情報を元に作成する手法
以下で弊社のフロントエンドエンジニアの村崎さんが紹介しているように、OpenAPI等のスキーマ情報からmockデータを作成する手法です。

https://times.hrbrain.co.jp/entry/2021/12/13/open-api-mock

APIから取得したデータをコード内でそのまま利用している場合は、同一のデータを手軽に取得できるためとても有用な手法です。

ただし、フロントのコード内でAPIデータを加工しそれをReactのComponentに渡している等、APIデータがそのまま使えないような場面では扱いづらいものとなってしまいます。

### 手動で必要なデータを作成する手法
以下のように手動で作成する手法です。

- APIのレスポンスをコピって作成
- コードの中で対象データをJSON.stringifyしてconsole.logでコンソールに出力しコピって作成
- 欲しいmockデータを型に沿って一から手動で頑張って作成

自由に作成できる、APIデータを加工したデータも作成できる、などの利点はありますが、手動で作るためどうしても手間がかかってしまいます。

# 半手動でmockデータを作成
本記事では新たな手法として、 `任意の箇所にmockデータ作成用コードを一時的に追加しmockデータを作成する` 手法を紹介します。

## なぜこの手法を使うのか
この手法を考えたきっかけとしては、現在担当しているサービスではAPIデータを加工・正規化して関数やComponent等に渡しているケースが多く、スキーマ情報を元に作成する手法を利用することが難しかったためです。

GraphQLであればフロント側でQueryを書いてレスポンス内容を決定することが可能なためデータの加工も不要になったりするのですが、私が担当しているサービスではREST APIかつデータ構造も複雑なものが多いため、フロントサイドでのデータ加工は必要不可欠な状況でした。

## 半手動mockデータ作成の流れ

1\.  データを取り出したい箇所に以下のような一時的なコードを追加し、そこでmockとして使いたいデータを渡す
```ts
testMock.setData(users)
```

2\. アプリを実行し、その際に一時的なコードが実行されるようにする


3\. グローバル関数を以下のようにブラウザのコンソールで実行すると、渡したデータが整形された状態でtsファイルとして保存される
```ts
window.DOWNLOAD_MOCK_DATA()
```

以上になっています。

## 実際のコード

#### 渡されたデータを保持してtsファイルとして出力する関数
mock作成をするためのコードということで割り切り、eslintとtsエラーチェックはオフにしています。
```ts:testMock.ts
/* eslint-disable */
export default {
  setData(data: any) {
    // @ts-ignore
    window.MOCK_DATA = data
    // @ts-ignore
    window.DOWNLOAD_MOCK_DATA = () => {
      // @ts-ignore
      const blob = new Blob([`export default ${JSON.stringify(data, null, '  ')}`], {
        type: 'text.plain',
      })
      const downloadLink = document.createElement('a')
      downloadLink.download = 'mock_data.ts'
      downloadLink.href = URL.createObjectURL(blob)
      downloadLink.dataset.downloadurl = [
        'text/plain',
        downloadLink.download,
        downloadLink.href,
      ].join(':')
      downloadLink.click()
    }
  },
}
```

#### testMock.tsの使用例
```ts:index.tsx
import UserList from './components/UserList'
import testMock from './testMock'

const users: User[] = [
  { name: 'user1' },
  { name: 'user2' },
  { name: 'user3' },
]

const IndexComponent = () => {
  testMock.setData(users)
  return <UserList users={users} />
}
```

この状態でアプリを実行して `testMock.setData` の箇所を通過した後に、コンソールで `window.DOWNLOAD_MOCK_DATA()` を叩くと、以下のようなmockデータファイルが保存されます。

```ts:mock_data.ts
export default [
  { name: "user1" },
  { name: "user2" },
  { name: "user3" }
]
```

あとはこのmockデータを以下のようにテストに組み込みます。

```tsx:components/__tests__/UserList.spec.tsx
import * as Enzyme from 'enzyme'
import usersMockData from './mock_data.ts'

describe('components', () => {
  it('UserList', () => {
    // 複雑なデータ構造の場合は型エラーが出てしまうことがあるが、テストだと割り切ってts-ignoreする
    // @ts-ignore
    const users = usersMockData as User[]
    Enzyme.mount(<UserList users={users} />)
  })
})
```

今回は一部のデータのみをmockデータとして保存しましたが、例えばcomponentのprops全てを一つのmockデータファイルとして保存するなどすれば、propsの多いcomponentのテストも楽になるかもしれません。


# まとめ
今回紹介した半手動なmockデータ作成の手法はいかがだったでしょうか。

スキーマ情報を元に作成したmockデータを使うことができれば一番ではあるのですが、APIやアプリの仕様によっては中々難しいことも多いかと思います。

そんなときに今回の手法を試していただけると嬉しいです。

HRBrainではこのようにフロントエンド開発を工夫しながら進められる仲間を募集しています。
気になった方はぜひ以下よりご応募ください。

https://www.hrbrain.co.jp/recruit