Next.js+TypeScript 環境で Storyshots を使う(Storybook 6 対応)
以前の「Next.js+TypeScript 環境で Storybook を使う」で用意した環境に Storybook を使って手軽にスナップショットテストが実行出来る Storyshots を導入します。
Storyshots は Storybook の Story を元にスナップショットテストをテストコードなし(初期設定は必要ですが)で自動的に行なってくれるとても便利なツールです。もし Storybook を整備しているなら是非導入を検討してみていただきたいツールのひとつです。
先に参考にしたドキュメントを掲載します。
この記事を書くために Jest の transpile まわりでかなり時間を使いましたが、以下3つを見ておけばだいたい解決するかと思います。
- Storybook 6 へのマイグレーション
https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#from-version-53x-to-60x - Next.js+TypeScript に Jest を導入
https://github.com/vercel/next.js/tree/canary/examples/with-typescript-eslint-jest - Storyshots の導入
https://github.com/storybookjs/storybook/tree/master/addons/storyshots/storyshots-core
Snapshot Testing とは
Snapshot Testing は様々なテスト手法のひとつです。Jest の解説記事がわかりやすいので一部引用します。
スナップショットのテストはUI が予期せず変更されていないかを確かめるのに非常に有用なツールです。
モバイルアプリでの典型的なスナップショットのテストケースでは、UIコンポーネントをレンダリングし、スクリーンショットを撮り、テストと一緒に保管しているスナップショットと比較します。 2つのスナップショットが一致しない場合テストは失敗します: 予期されない変更があったか、参照するスナップショットが新しいバージョンのUIコンポーネントに更新される必要があるかのどちらかです。
https://jestjs.io/docs/ja/26.2/snapshot-testing
上記のとおり現在の状態を正しいと定義し、その出力結果をスクリーンショットや json などの状態でスナップショットとして保存しておき、コード変更後に新たな出力結果とスナップショットを比較します。そうすることで意図しない変更が発生していないかを検知する仕組みです。
また、 Jest ではスクリーンショットではなく React などのコンポーネントのツリー構造をコードとして保存するのでテスト結果も diff で表示され変更箇所を把握しやすくなっているのも特徴です。
Storybook のメジャーバージョンをあげる
ということで早速導入したいところですが、Storybook 6 が2020年8月11日にリリースされたので5から6へあげます。
Storybook 6 では Zero-config Storybook として Built-in TypeScript support になりました 🎉
これで以前の記事の1/3が要らない子になりましたね。後で追記しておきます。。
Storybook のメジャーバージョンをあげる作業のPRは下記です。
- https://github.com/Gaji-Labo/demo-storybook-with-next-typescript/pull/5/files
- https://github.com/Gaji-Labo/demo-storybook-with-next-typescript/pull/7/files
メジャーバージョンを 6 にあげる
まずは Storybook 関連のパッケージを upgrade します。
$ yarn upgrade @storybook/addon-links @storybook/react
不要になった ts 関連の記述を削除
Storybook が ts 対応したので不要になったパッケージと設定ファイルを削除します。
$ yarn remove babel-loader
$ rm .storybook/main.js // ここで削除した main.js は次で再度作成します
※PRでは babel-preset-react-app
も削除していますが、後続の作業で必要でした。
config の指定方法が変わったので対応
正確には v6 ではなく v5.3 3 での変更ですが、.storybook/addons.js
と .storybook/config.js
が deprecated になりました。
どちらも v7 では非対応になるとのことでこのタイミングで修正しておきます。
詳しくは: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#to-mainjs-configuration
story の場所や使用する addon 設定は main.js にまとめることになりました。
module.exports = {
stories: ["../components/**/*.stor@(y|ies).[tj]sx"],
addons: ["@storybook/addon-links/register"],
};
Welcome
という story をナビゲーションの一番上に移動する処理 storySort
を config.js
に書いていましたが、ブラウザで実行する処理は preview.js
に移動になりました。
(ついでにソートの仕方も目的にあわせてシンプルにしました。)
const addParameters = require("@storybook/react").addParameters;
addParameters({
options: {
storySort: {
order: ["Welcome", "README"],
},
},
});
Jest の導入
Storyshots は Jest を使っているのでまずは Jest を導入します。
このプロジェクトの Next.js はゼロから create-next-app しているので Jest が入っていません。Next.js 公式の下記を参考に Jest の導入をしました。
Jest の導入に関するPRは下記の前半です。
まずは使用するパッケージをインストールします。
$ yarn add --dev \
jest \
jest-watch-typeahead \
react-test-renderer \
babel-jest \
babel-preset-react-app \
@types/jest
そして Next.js 公式の examples/with-typescript-eslint-jest
を参考に設定ファイルを作成します。
とくに .babelrc
で presets
に next/babel
を指定しているところが最初は分からずドハマりしていました。Next.js 関連は最初に公式のサンプルから確認するのがおすすめです。
{
"presets": ["next/babel"]
}
動作確認用に true
が toBeTruthy
になるかという雑なテストを追加しました。
import "@jest/globals";
describe("jest 動作確認", () => {
test("true toBeTruthy", () => {
expect(true).toBeTruthy();
});
});
これで package.json の test
を追加して yarn test
すれば jest の動作確認ができました。
"scripts": {
...
"test": "jest"
},
Storyshots の導入
Jest が導入できて Storybook が正しく設定できていれば Storyshots の導入自体は簡単です。パッケージをインストールし、Storyshots 用のテストコードを一つ用意すれば導入は完了です。
今回は通常の test と Storyshots を分けて実行したかったので少し手順が多くなっています。
Storyshots の導入に関するPRは下記の後半です。
Storyshots の基本的な導入
@storybook/addon-storyshots
をインストールし
$ yarn add --dev @storybook/addon-storyshots
Storyshots.test.ts
を作成します。
import initStoryshots from "@storybook/addon-storyshots";
initStoryshots();
これで $ jest
を実行すれば jest の他のテストと一緒に Storyshots も実行されます。
Storyshots のみ実行するように script を分ける
$ yarn storyshots
で Storyshots のみ実行できるように package.json に script を追加し、専用の jest config を用意します。
"scripts": {
...
"storyshots": "jest --config ./jest.storyshots.config.js",
"test": "jest"
},
const baseConfig = require("./jest.config");
module.exports = {
...baseConfig,
name: "Storyshots",
displayName: "storyshots",
testMatch: ["<rootDir>/Storyshots.test.ts"],
};
Snapshots テストを実行してみる
初回実行時
すべての準備がととのったので Storyshots を実行します。
初回実行時は自動的にスナップショットが生成されてテストがパスします。
$ yarn storyshots
※複数の Story で同じ `Welcome` タイトルを使っているという警告がでていますが、今回とは関係ないので無視します。
これで下記のスナップショットファイルが生成されます。
- __snapshots__/Storyshots.test.ts.snap
Story を編集したあとのテスト実行
その後 Button.withText
Story を編集してもう一度実行すると下記のように差分を検知しテストが fail します。
Hello Button
が Hello Button!
に変わっているのがわかります。
スナップショットの更新
もしこの変更が正しい場合はスナップショットの更新 jest --updateSnapshot
を行います。yarn storyshots
の本体は jest
なので --updateSnapshot
オプションをつけて実行するだけです。
$ yarn storyshots --updateSnapshot
これでさきほど生成された __snapshots__/Storyshots.test.ts.snap
が更新されて、もう一度 $ yarn storyshots
を実行しても fail しなくなります 🎉
Storybook を資産として有効活用していきたい
Gaji-Labo ではコンポーネントを Storybook 上で実装してからアプリケーションに反映していくフローを採用しています。
ただ、長く運用しているプロジェクトでは Storybook のメンテナンスが行き届かなくなっていくことも多々あります。その理由のなかに「Storybook をメンテするメリットが薄い」ためにモチベーションが維持できないというのもあると感じています。
Storyshots の導入で少しでも Storybook をメンテナンスする動機づけになればいいなとおもっています。Gaji-Labo の手から離れてもコンポーネントの秩序が保たれてプロダクトの寿命を少しでも伸ばし日々の開発効率を下支えできるような状態を作り続けたいです。
開発のお悩み、フロントエンドから解決しませんか?
あなたのチームのお悩みはなんですか?
「バックエンドエンジニアにフロントエンドまで任せてしまっている」
「デザイナーに主業務以外も任せてしまっている」
「すべての手が足りず細かいことまで手が回らない」
役割や領域を適切に捉えてカバーし、チーム全体の生産性と品質をアップするお手伝いをします。
フロントエンド開発に関わるお困りごとがあれば、まずは一度お気軽に Gaji-Labo にお声がけください。
オンラインでのヒアリングとフルリモートでのプロセス支援に対応しています。
リモートワーク対応のパートナーをお探しの場合もぜひ弊社にお問い合わせください!
お悩み相談はこちらから!