Skip to content
A minimum example about how to use Storybook & Storyshots for React app
JavaScript TypeScript HTML
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.jest
.storybook
.vscode
public
src
stories
.babelrc
.gitignore
LICENSE
README.md
jest.config.js
package-lock.json
package.json
tsconfig.json
webpack.config.js

README.md

Storybook + Storyshotsでコンポーネントの保守可能なUIテストを行う

TL;DR

  • プロジェクト内のコンポーネント一覧を見るためにStorybookを使う
  • コンポーネント単位でpropsの値の変更による表示の確認が可能になる
  • Storybookのコードが腐らないようにStoryshotsを使ってスナップショットテストする
  • サンプルコード: shisama/react-storybook-storyshots-example

今回の例コンポーネント

今回の記事ではサンプルとして以下の小さなコンポーネントを例に進めたいと思います。

import React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faHeart } from '@fortawesome/free-regular-svg-icons';

type Props = {
  count: number;
  onClick: React.MouseEventHandler;
};

export const Like = ({ count, onClick }: Props) => {
  return (
    <>
      <div onClick={onClick}>
        <FontAwesomeIcon icon={faHeart} />
        {count}
      </div>
    </>
  );
};

Storybook

pros

コンポーネントを一覧をブラウジングできるツール。 コンポーネントごとにレビューするときとかにも使える 渡すpropsによって表示が変わるコンポーネントとかは複数パターンをイッキ見できて便利

cons

storybook用のコードの保守コスト ビルドしてプロダクションでしか確認しないメンバーがいるとstorybookのコードは腐る。コンポーネントを修正したときにstorybookのコードは後回しとかになって保守されなくなっていく。 コンポーネントの一覧は表示できるが、自動テストするものではないため人力での確認となる

Storyshots

pros

storybookのコードを使いスナップショットテストが可能になる。 storybook用に書いたコードが実質テストコードとなるため、テストコードとstorybookのコードを二重管理しなくて良い。 コンポーネントを修正したときにstorybookのコードも修正しないとスナップショットテストが失敗する

cons

Storyshots自体の保守が発生する Storyshotsの設定が必要

手順

Storybook導入(React用)

Storybook for React

npx -p @storybook/cli sb init --type react

@storybook/cliはstorybookの設定などを自動で行ってくれるCLIツールです。

Storybook + TypeScript + React

StorybookはBabelを使ってトランスパイルを行います。そのため、TypeScript用にBabelの設定を行う必要があります。

BabelでTypeScriptやReactをトランスパイルするためのpresetをインストールします。

npm i -D @babel/core @babel/preset-typescript @babel/preset-react

次にBabelの変換を行うための.babelrcをプロジェクト直下に作ります。

{
  "presets": [
    "@babel/preset-typescript",
    "@babel/preset-react"
  ]
}

次にStorybookでTypeScriptのビルドを行うために.storybook配下にwebpack.config.jsを作成します。

module.exports = ({ config }) => {
  config.module.rules.push({
    test: /\.tsx?$/,
    use: [
      {
        loader: require.resolve('ts-loader')
      },
      {
        loader: require.resolve('react-docgen-typescript-loader')
      }
    ]
  });
  config.resolve.extensions.push('.ts', '.tsx');
  return config;
};

react-docgen-typescript-loaderをインストールしなければいけません。

npm i -D react-docgen-typescript-loader

react-docgen-typescript-loaderはTypeScriptで作られたReactコンポーネントをドキュメント化するのに必要なWebpackのloaderです。
GitHub - strothj/react-docgen-typescript-loader: Webpack loader to generate docgen information from Typescript React components.

最後にコンポーネントのStorybook用の表示ファイルを作成します。 前述の@storybook/cliによってstories/index.stories.jsが作成されています。
このファイルをLikeコンポーネント用に書き換えましょう。 ファイル名は任意です。index.stories.tsxでもいいですが、Like.tsxLike.stories.tsxなどわかりやすい名前にしておくといいかと思います。
ファイル名は任意ですが、拡張子は.tsxにしてください。
例えば、Like数が0の場合と1の場合の表示を確認してみましょう。

import React from "react";

import { storiesOf } from "@storybook/react";
import { Like } from "../src/components/Like";

storiesOf("Like", module).add("0", () => (
  <Like count={0} onClick={() => {}} />
)).add("1", () => (
  <Like count={1} onClick={() => {}} />
));

前述の@storybook/cliによって.storybook/config.jsが作成されています。
Storybookで前述のLike.stories.tsxを読み込ませるためにこの.storybook/config.jsを編集します。

import { configure } from '@storybook/react';

// このrequire.contextで読み込む対象の拡張子を.jsから.tsxに編集
// const req = require.context('../stories', true, /\.stories\.js?$/);
const req = require.context('../stories', true, /\.stories\.tsx?$/);
function loadStories() {
  req.keys().forEach(filename => req(filename));
}

configure(loadStories, module);

ここまで設定するとStorybookは実行可能となりLikeのstoriesがブラウザで表示できます。

npm run storybook

Image from Gyazo

Storyshots導入

storybook/addons/storyshots/storyshots-core at master · storybookjs/storybook · GitHub

Storyshotsの導入の前にテストランナーをインストールします。
今回はJestを使います。
Getting Started · Jest

npm i -D jest babel-jest 

TypeScriptを使う場合は@babel/preset-typescriptも必要。 BabelでTypeScriptをトランスパイルしているとJestでテストを実行する時に型検査をしてくれません。
Jest実行時に型検査をしたい場合はts-jestを使う必要がありますが、これまで見てきたようにStorybookはBabelに依存しています。Storyshotsも同様にBabelで設定した方が簡単だと個人的には思います。

次にStoryshotsをインストールします。

npm i -D @storybook/addon-storyshots

次にbabel-plugin-require-context-hookをインストールします。

npm i -D babel-plugin-require-context-hook

babel-plugin-require-context-hook/registerを実行するために.jest/register-context.tsというファイルを作ります。

import registerRequireContextHook from 'babel-plugin-require-context-hook/register';
registerRequireContextHook();

次にStoryshotsのテストファイルstories/__tests__/Like.test.tsを作成します。

import initStoryshots from '@storybook/addon-storyshots';

initStoryshots();

次にJestの設定ファイルjest.config.jsを作成します。setupFilesはJest実行時に前述の.jest/register-context.tsを、testMatchはStoryshotsのテストファイルを読み込むようにします。

module.exports = {
  setupFiles: ["<rootDir>/.jest/register-context.ts"],
  testMatch: ["<rootDir>/stories/__tests__/*.test.ts"]
};

Jestの設定ファイルの各プロパティの詳細についてはConfiguring Jest · Jestをご確認ください。

最後にBabelにpluginを設定します。

{
  "presets": ["..."],
  "plugins": ["..."],
  "env": {
    "test": {
      "plugins": ["require-context-hook"]
    }
  }
}

次にjest.config.jsから読み込むファイルの変換を行うために@babel/preset-envをインストールします。

npm i -D @babel/preset-env

@babel/preset-env.babelrcに設定します。

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "node": "current"
        }
      }
    ],
    "@babel/preset-typescript",
    "@babel/preset-react"
  ],
  ...
}

ここまでで.babelrcは以下のようになっています。

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "node": "current"
        }
      }
    ],
    "@babel/preset-typescript",
    "@babel/preset-react"
  ],
  "env": {
    "test": {
      "plugins": ["require-context-hook"]
    }
  }
}

Storyshotsはreact-test-rendererに依存しているので、Reactで動かすためにはreact-test-rendererをインストールする必要があります。

npm i -D react-test-renderer

実行

Jestでのテストを実行するためにpackage.jsontestタスクを追加します。

  "scripts": {
    ...
    "test": "jest"
    ...
  },

実行は以下のコマンドから行えます。

npm test

実行結果

実行するとstories/__tests__/__snapshots__というディレクトリが作成され、Like.test.ts.snapが作成されます。

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Storyshots Like 0 1`] = `
<div
  onClick={[Function]}
>
  <svg
    aria-hidden="true"
    className="svg-inline--fa fa-heart fa-w-16 "
    data-icon="heart"
    data-prefix="far"
    focusable="false"
    role="img"
    style={Object {}}
    viewBox="0 0 512 512"
    xmlns="http://www.w3.org/2000/svg"
  >
    <path
      d="M458.4 64.3C400.6 15.7 311.3 23 256 79.3 200.7 23 111.4 15.6 53.6 64.3-21.6 127.6-10.6 230.8 43 285.5l175.4 178.7c10 10.2 23.4 15.9 37.6 15.9 14.3 0 27.6-5.6 37.6-15.8L469 285.6c53.5-54.7 64.7-157.9-10.6-221.3zm-23.6 187.5L259.4 430.5c-2.4 2.4-4.4 2.4-6.8 0L77.2 251.8c-36.5-37.2-43.9-107.6 7.3-150.7 38.9-32.7 98.9-27.8 136.5 10.5l35 35.7 35-35.7c37.8-38.5 97.8-43.2 136.5-10.6 51.1 43.1 43.5 113.9 7.3 150.8z"
      fill="currentColor"
      style={Object {}}
    />
  </svg>
  0
</div>
`;

exports[`Storyshots Like 1 1`] = `
<div
  onClick={[Function]}
>
  <svg
    aria-hidden="true"
    className="svg-inline--fa fa-heart fa-w-16 "
    data-icon="heart"
    data-prefix="far"
    focusable="false"
    role="img"
    style={Object {}}
    viewBox="0 0 512 512"
    xmlns="http://www.w3.org/2000/svg"
  >
    <path
      d="M458.4 64.3C400.6 15.7 311.3 23 256 79.3 200.7 23 111.4 15.6 53.6 64.3-21.6 127.6-10.6 230.8 43 285.5l175.4 178.7c10 10.2 23.4 15.9 37.6 15.9 14.3 0 27.6-5.6 37.6-15.8L469 285.6c53.5-54.7 64.7-157.9-10.6-221.3zm-23.6 187.5L259.4 430.5c-2.4 2.4-4.4 2.4-6.8 0L77.2 251.8c-36.5-37.2-43.9-107.6 7.3-150.7 38.9-32.7 98.9-27.8 136.5 10.5l35 35.7 35-35.7c37.8-38.5 97.8-43.2 136.5-10.6 51.1 43.1 43.5 113.9 7.3 150.8z"
      fill="currentColor"
      style={Object {}}
    />
  </svg>
  1
</div>
`;
You can’t perform that action at this time.