4
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?

More than 1 year has passed since last update.

ファイル選択を含む機能を jest / storybook それぞれでテストする方法

Posted at

背景

ファイルを選択する機能を含むコンポーネントをテストする際に、テスト用の File オブジェクトを取得する必要がある。
テストを行う際、従来は Node.js 上で Jest を起動してテストすることが多かったが、最近は storybook が推奨する playwright などブラウザのランタイム上でテストを実行することも増えてきたのではないかと思う。
ランタイムが Node.js の時と playwright のようなブラウザの時とでファイルを取得するテクニックが異なるので、それぞれでうまくいった方法を共有したい。

環境

  • node: v16.18.1
  • next: v13.2.1
  • react: v18.2.0
  • jest: v29.2.1
  • @storybook/react: v6.5.16
  • @testing-library/react: v13.4.0
  • @testing-library/user-event: v13.5.0

テスト対象のコンポーネント

下記のような、選択したファイルを Data URL として読んでプレビュー画像として表示するコンポーネントのテストを考える。

pages/index.tsx
import Image from "next/image";
import { useState } from "react";

const TestPage: React.FC = () => {
  const [fileData, setFileData] = useState<string | null>(null);

  const onSelectFile = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.currentTarget.files?.[0];

    if (file) {
      const reader = new FileReader();

      reader.onload = () => {
        setFileData(reader.result as string);
      };

      reader.readAsDataURL(file);
    } else {
      setFileData(null);
    }
  };

  return (
    <div>
      <input
        id="file"
        name="file"
        type="file"
        aria-label="file-select"
        onChange={onSelectFile}
      />
      {fileData && (
        <div>
          <Image
            src={fileData}
            width={515}
            height={645}
            alt="avatar"
            aria-label="preview"
          />
        </div>
      )}
    </div>
  );
};

export default TestPage;

ファイル選択後の storybook での見た目はこんな感じ。
スクリーンショット 2023-03-26 0.32.24.png

storybook の場合

storybook の場合はブラウザの fetch API を利用してファイルを blob 形式で取得することができるので、それを利用する。
まず public/ ディレクトリ内に該当の画像ファイルを配置しておく。

├── public
│   └── dummy.png
├── src
│   ├── pages
│   │   ├── _app.tsx
│   │   ├── _document.tsx
│   │   ├── index.stories.tsx
│   │   ├── index.test.tsx
│   │   └── index.tsx
│   └── styles
│       ├── Home.module.css
│       └── globals.css
└── tsconfig.json

次に storybook をビルドする際のコマンドに、 public ディレクトリ内のファイルをサーブするオプションを追加する。

# 開発時
$ start-storybook -s public
# デプロイ時
$ build-storybook -s public

そして下記のように story を実装する。

pages/index.stories.tsx
import { ComponentMeta, ComponentStoryObj } from "@storybook/react";
import { within } from "@storybook/testing-library";
import userEvent from "@testing-library/user-event";

import TestPage from "./index";

type Story = ComponentStoryObj<typeof TestPage>;

export default {
  component: TestPage,
} as ComponentMeta<typeof TestPage>;

export const Default: Story = {
  async play({ canvasElement }) {
    const { getByLabelText } = within(canvasElement);

    // public/ 内の画像ファイルを Blob 形式で fetch API 経由で取得する
    const response = await fetch("/dummy.png");
    const blob = await response.blob();
    const file = new File([blob], "image.png");

    userEvent.upload(getByLabelText("file-select"), file);
  },
};

fetch API を用いて取得した File オブジェクトを userEvent.upload に渡せば、無事にファイル選択をシミュレートできる。

jest の場合

先ほど public/ 内に配置したファイルを fs 経由で取得する。

pages/index.test.tsx
import fs from "fs";

import { render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

import TestPage from "./index";

describe("when a file is selected", () => {
  it("shows preview image", async () => {
    const { findByRole, getByLabelText } = render(<TestPage />);

    const buffer = fs.readFileSync("public/dummy.png");
    const file = new File([buffer], "dummy.png");

    userEvent.upload(getByLabelText("file-select"), file);

    expect(await findByRole("img", { name: "preview" })).toBeInTheDocument();
  });
});

実行してみると無事に下記の通り PASS した。

$ npm run test -- pages/index.test.tsx
 PASS  pages/index.test.tsx
  when a file is selected
    ✓ shows preview image (201 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.638 s, estimated 1 s
Ran all test suites matching /src\/pages\/index.test.tsx/i.
4
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
4
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?