1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

1人フロントエンドAdvent Calendar 2024

Day 12

Subpath ImportsでStorybook中でだけ特定のファイルをモックする

Posted at

Storybookではコンポーネントの計算を行い、その結果をStoryとして出力します。
いくつかのコンポーネントは外部のデータソースをもとに計算されます。

APIを用いたデータの取得はmswを用いたモックで一貫したStoryを提供できましたが、ormを用いたデータの取得や現在時刻に依存するコンポーネントはモックすることが難しくStorybookを利用するためだけに一工夫していました(Presenter層を作成するなど)。

Storybookの8.1からはNode.jsの機能であるSubpath Importsを用いてあらゆるデータのやり取りをモックできます。

Subpath Importsはpackage.jsonを利用して環境に合わせてインポート先を変更できる機能です。

package.json
{
  "imports": {
    "#src/lib/kv": {
      "storybook": "./src/lib/kv.mock.ts",
      "default": "./src/lib/kv.ts"
    },
    "#*": "./*"
  }
}

上記のように定義した状態で、#src/lib/kvから読み込みを行うとstorybook環境では./src/lib/kv.mock.tsを参照し、それ以外では./src/lib/kv.tsを読み込むようになります。

// storybookからだけ代わりに./src/lib/kv.mock.tsを読み込む
// それ以外は./src/lib/kv.tsを読み込む
import { get } from '#src/lib/kv';

これを利用して行うのが今回紹介するモックです。
説明に利用するコードはStackBlitzに準備しました。

まずはモックする処理が記載されたファイルから紹介します。

kv.ts
import { kv } from '@vercel/kv';

export async function get(key: string): Promise<string | null> {
  try {
    const value = await kv.get<string>(key);
    return value;
  } catch (error) {
    return null;
  }
}
kv.mock.ts
import { fn } from '@storybook/test';
import * as actual from './kv';

export const get = fn(actual.get).mockName('kv:get');

kv.ts@vercel/kvのラッパーでVercel KVからデータを取得しています。このファイルをkv.mock.tsでモックしています。
モックは@storybook/testfnで対象の関数をベースにモックしています。モックが返す値は利用する側で決めます。fn(actual.get)とすることでモックした関数に元の関数の型を継承することができます。

Storyの対象コンポーネントは以下のようになっています。

import { get } from '#src/lib/kv';
import { FC } from 'react';

export const Example: FC = async () => {
  const value = await get('example');
  if (!value) {
    return <p>値がありません</p>;
  }
  return <p>{value}</p>;
};

処理を#src/lib/kvからインポートすることで、普段の利用は./src/lib/kv.ts、Storybookでは./src/lib/kv.mock.tsを読み込みます。

Storyを定義するファイルは以下のとおりです。

import type { Meta, StoryObj } from '@storybook/react';
import { Example } from '#src/stories/example';
import { get } from '#src/lib/kv.mock';

const meta = {
  component: Example,
} satisfies Meta<typeof Example>;
export default meta;

type Story = StoryObj<typeof meta>;

export const Default = {
  beforeEach: () => {
    get.mockResolvedValue('12');
  },
} satisfies Story;

export const Empty = {
  beforeEach: () => {
    get.mockResolvedValue(null);
  },
} satisfies Story;

StoryごとにbeforeEachでモックが返す値を決めています。

この方法を使えば、どんな処理であっても別のファイルに切り出すことStorybook上で型安全にモックすることができます。

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?