個人的にめちゃくちゃハマったので自分用にメモ
(後述の通り、優先度は低いところだがまだ解消していないところもある)
各パッケージのバージョンが上がっていくと事態は変わっていくと思われるので注意
(とりあえずnext@12.3.0, React@18.2, storybook@6.5.10, TypeScript@4.8.3, jest@29.0.3くらいまでで確認)
TL;DR;
-
package.json
のresolutions
を見直す(参考):
"resolutions": {
"react-test-renderer": "18.1.0"
}
- または、custom-rendererを使う (追記)
// testing-libraryによる例:
import initStoryshots from '@storybook/addon-storyshots';
import { render } from '@testing-library/react';
const reactTestingLibrarySerializer = {
print: (val, serialize, indent) => serialize(val.container.firstChild),
test: (val) => val && val.hasOwnProperty('container'),
};
initStoryshots({
renderer: render,
snapshotSerializers: [reactTestingLibrarySerializer],
});
-
***.stories.mdx
に対してエラーが出る- 解消法は現時点で確認出来ていない
- (mdxに対するエラーがあってもテスト自体はパスするので必要性が無ければパスしても良さそう)
動機
- 作成したcomponentsやページなどが意図せずデグレしていないか、snapshotテストを行うことで担保したい
- 差分がある場合も具体的にどのように変わるかが確認しやすくなりそう
- storybookを使う予定であり、各ストーリーと上記のsnapshotを取っておきたくなるような対象はほぼイコールと考えられるため、一緒に管理出来るようにしたい
- 作成したstoryからそのままsnapshotを取れれば一番楽そう
- StoryShotsを使えばゼロコンフィグで自動的にすべての
*.stories.(ts|tsx)?
からsnapshotテストを生成出来そうなので便利そう
前提条件: 検証環境
- Ubuntu22.04 @WSL2(Windows11)
- node@v16.16.0
- yarn@1.22.19
検証
Next.js環境セットアップ
ベースの環境として、nextjsのexamplesにあるwith-jestを使う
→個人的にNext.jsのプロジェクトで使おうと思っていたのと、TypeScript+Jestの環境を自動で作成出来るため
# 今回は`storyshots-test`ディレクトリにプロジェクトを作成する
yarn create next-app --example with-jest storyshots-test
# 作成したプロジェクトに移動
cd storyshots-test
2022/9/10現在だと、以下のような感じのファイルが生成される:
(詳細)
tree -a -I ".git|node_modules"
.
├── .eslintrc.json
├── .gitignore
├── .swc
│ └── plugins
│ └── v3
├── README.md
├── __mocks__
│ ├── fileMock.js
│ └── styleMock.js
├── __tests__
│ ├── __snapshots__
│ │ └── snapshot.tsx.snap
│ ├── index.test.tsx
│ └── snapshot.tsx
├── jest.config.js
├── jest.setup.js
├── next-env.d.ts
├── package.json
├── pages
│ ├── _app.tsx
│ ├── index.module.css
│ └── index.tsx
├── public
│ ├── favicon.ico
│ └── vercel.svg
├── styles
│ └── global.css
├── tsconfig.json
├── types.d.ts
└── yarn.lock
storybook導入
npx storybook init
などでstorybookをインストール出来る
インストール後のディレクトリ、ファイル
tree -a -I ".git|node_modules"
.
├── .eslintrc.json
├── .gitignore
├── .storybook
│ ├── main.js
│ └── preview.js
├── .swc
│ └── plugins
│ └── v3
├── README.md
├── __mocks__
│ ├── fileMock.js
│ └── styleMock.js
├── __tests__
│ ├── __snapshots__
│ │ └── snapshot.tsx.snap
│ ├── index.test.tsx
│ └── snapshot.tsx
├── jest.config.js
├── jest.setup.js
├── next-env.d.ts
├── package.json
├── pages
│ ├── _app.tsx
│ ├── index.module.css
│ └── index.tsx
├── public
│ ├── favicon.ico
│ └── vercel.svg
├── stories
│ ├── Button.stories.tsx
│ ├── Button.tsx
│ ├── Header.stories.tsx
│ ├── Header.tsx
│ ├── Introduction.stories.mdx
│ ├── Page.stories.tsx
│ ├── Page.tsx
│ ├── assets
│ │ ├── code-brackets.svg
│ │ ├── colors.svg
│ │ ├── comments.svg
│ │ ├── direction.svg
│ │ ├── flow.svg
│ │ ├── plugin.svg
│ │ ├── repo.svg
│ │ └── stackalt.svg
│ ├── button.css
│ ├── header.css
│ └── page.css
├── styles
│ └── global.css
├── tsconfig.json
├── types.d.ts
└── yarn.lock
12 directories, 41 files
stories
ディレクトリと.storybook
ディレクトリが生成されている。
また、ここまででpackage.json
の中身は以下のような感じ:
package.json
{
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"test": "jest --watch",
"test:ci": "jest --ci",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"dependencies": {
"next": "latest",
"react": "^18.1.0",
"react-dom": "^18.1.0"
},
"devDependencies": {
"@babel/core": "^7.19.0",
"@storybook/addon-actions": "^6.5.10",
"@storybook/addon-essentials": "^6.5.10",
"@storybook/addon-interactions": "^6.5.10",
"@storybook/addon-links": "^6.5.10",
"@storybook/builder-webpack5": "^6.5.10",
"@storybook/manager-webpack5": "^6.5.10",
"@storybook/react": "^6.5.10",
"@storybook/testing-library": "^0.0.13",
"@testing-library/jest-dom": "5.16.4",
"@testing-library/react": "13.2.0",
"@testing-library/user-event": "14.2.0",
"@types/react": "18.0.9",
"babel-loader": "^8.2.5",
"jest": "28.1.0",
"jest-environment-jsdom": "28.1.0",
"typescript": "4.6.4"
}
}
StoryShots導入(失敗)
公式の説明に従って導入してみる
yarn add -D @storybook/addon-storyshots
次に、__tests__/storyshots.test.ts
を以下の内容で作成
import initStoryshots from '@storybook/addon-storyshots';
initStoryshots();
公式によればこれだけでOKらしい?ので、テストを実行してみる
yarn jest
PASS __tests__/index.test.tsx
PASS __tests__/snapshot.tsx
FAIL __tests__/storyshots.test.ts
● Console
console.error
Unexpected error while loading ./Introduction.stories.mdx: Cannot use import statement outside a module
Jest encountered an unexpected token
Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to su
pport such syntax.
Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.
By default "node_modules" folder is ignored by transformers.
Here's what you can do:
• If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
• If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/configuration
For information about custom transformations, see:
https://jestjs.io/docs/code-transformation
(...)
● Storyshots › Example/Page › Logged Out
TypeError: Cannot read properties of undefined (reading 'current')
at warnIfNotScopedWithMatchingAct (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:14999:31)
at updateContainer (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:16413:7)
at create (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:17149:3)
at getRenderedTree (node_modules/@storybook/addon-storyshots/dist/ts3.9/frameworks/react/renderTree.js:24:18)
at node_modules/@storybook/addon-storyshots/dist/ts3.9/test-bodies.js:9:24
at Object.<anonymous> (node_modules/@storybook/addon-storyshots/dist/ts3.9/api/snapshotsTestsTemplate.js:29:24)
● Storyshots › Example/Page › Logged In
TypeError: Cannot read properties of undefined (reading 'current')
at warnIfNotScopedWithMatchingAct (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:14999:31)
at updateContainer (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:16413:7)
at create (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:17149:3)
at getRenderedTree (node_modules/@storybook/addon-storyshots/dist/ts3.9/frameworks/react/renderTree.js:24:18)
at node_modules/@storybook/addon-storyshots/dist/ts3.9/test-bodies.js:9:24
at Object.<anonymous> (node_modules/@storybook/addon-storyshots/dist/ts3.9/api/snapshotsTestsTemplate.js:29:24)
Test Suites: 1 failed, 2 passed, 3 total
Tests: 8 failed, 2 passed, 10 total
Snapshots: 1 passed, 1 total
Time: 2.916 s
Ran all test suites.
error Command failed with exit code 1.
のような感じで大量にエラーが発生する。
なお、よくよく見るとエラーは2種類あり、
- 自動生成された
Introduction.stories.mdx
に対するエラー- 要するに
.mdx
形式のファイルをうまく扱えていない - 実はこのエラーが残っていてもテストは通るので気持ち悪いが残していてもOK。または、不要であればファイルを消してしまうなど。(対処が面倒かつ支障が出ない場合)
- 要するに
- 各
*.stories.tsx
に対するTypeError: Cannot read properties of undefined (reading 'current')
- エラー詳細: b
at warnIfNotScopedWithMatchingAct (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:14999:31)
- 要するに
react-test-renderer
周りのエラー
- エラー詳細: b
最低限、後者は対処しなくてはいけない
StoryShotsのエラー解消
react-test-rendererのエラーへの対処
それっぽいissueがあるので、それを参考にしていく
結論、react-test-renderer
のバージョンに原因がある?らしく、package.json
のresolutions
で条件を指定することで解消出来るらしい
+ "resolutions": {
+ "react-test-renderer": "18.2.0"
+ }
こうするとsnapshotテストはちゃんと通るようになる(※mdx
ファイルに対するエラーは残る)
yarn jest
PASS __tests__/snapshot.tsx
PASS __tests__/index.test.tsx
PASS __tests__/storyshots.test.ts
● Console
console.error
Unexpected error while loading ./Introduction.stories.mdx: Cannot use import statement outside a module
Jest encountered an unexpected token
Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to su
pport such syntax.
Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.
By default "node_modules" folder is ignored by transformers.
Here's what you can do:
• If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
• If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/configuration
For information about custom transformations, see:
https://jestjs.io/docs/code-transformation
Test Suites: 3 passed, 3 total
Tests: 10 passed, 10 total
Snapshots: 9 passed, 9 total
Time: 3.159 s
Ran all test suites.
Done in 4.26s.
一見NGっぽく見えるが、ちゃんとテストはすべて成功し、各ストーリのsnapshot: __tests__/__snapshots__/storyshots.test.ts.snap
も無事に作成されている。
*.mdx
形式のファイルを扱う必要性が無ければとりあえずこれでOK。
(追記)custom rendererを使う場合
エラーの原因がreact-test-renderer
を使っていることに起因しているなら、冷静に使わなければ良いのではという。
ちょうど、
に、rendererを自前のものを指定できるオプションが紹介されている。
今回のセットアップ(nextjsのwith-jestテンプレート)でははじめから@testing-library/react
がインストールされており、実際unittestの作成で入れているケースが多いと思われるため、これを使うので良さそう。
- "resolutions": {
- "react-test-renderer": "18.2.0"
- }
# yarnコマンドを実行して、yarn.lockの内容を更新(元に戻す)
yarn
__tests__/storyshots.test.ts
の内容をアップデートする:
import initStoryshots from '@storybook/addon-storyshots';
+ import { render } from '@testing-library/react';
+ const reactTestingLibrarySerializer = {
+ print: (val, serialize, indent) => serialize(val.container.firstChild),
+ test: (val) => val && val.hasOwnProperty('container'),
+ };
- initinitStoryshots();
+ initStoryshots({
+ renderer: render,
+ snapshotSerializers: [reactTestingLibrarySerializer],
+ });
この方法でも各storyのsnapshotは無事に作成される。
ただし、rendererを変えているぶんsnapshotの中身は変わるので注意
snapshotの差分(抜粋)
(...)
@@ -30,12 +20,7 @@ exports[`Storyshots Example/Button Primary 1`] = `
exports[`Storyshots Example/Button Secondary 1`] = `
<button
- className="storybook-button storybook-button--medium storybook-button--secondary"
- style={
- Object {
- "backgroundColor": undefined,
- }
- }
+ class="storybook-button storybook-button--medium storybook-button--secondary"
type="button"
>
Button
@@ -44,12 +29,7 @@ exports[`Storyshots Example/Button Secondary 1`] = `
exports[`Storyshots Example/Button Small 1`] = `
<button
- className="storybook-button storybook-button--small storybook-button--secondary"
- style={
- Object {
- "backgroundColor": undefined,
- }
- }
+ class="storybook-button storybook-button--small storybook-button--secondary"
type="button"
>
Button
@@ -59,7 +39,7 @@ exports[`Storyshots Example/Button Small 1`] = `
exports[`Storyshots Example/Header Logged In 1`] = `
<header>
<div
- className="wrapper"
+ class="wrapper"
>
<div>
<svg
@@ -70,7 +50,7 @@ exports[`Storyshots Example/Header Logged In 1`] = `
>
<g
fill="none"
- fillRule="evenodd"
+ fill-rule="evenodd"
>
<path
d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z"
(...)
mdx形式のstoryも扱う場合(未完)
TODO(余裕があれば検証&追記)
それらしいissue:
https://github.com/storybookjs/storybook/issues/7223
などもあるが、手元ではうまくいっていない。
おまけ パッケージのバージョンアップ
使用するライブラリのバージョンを上げても同様に動くか検証した。
厳密にはsnapshotの形式が変わったり、というのはあったものの、storyshotsが無事に動くという意味では検証が取れた。
最終的に検証したときのpackage.json
:
package.json
{
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"test": "jest --watch",
"test:ci": "jest --ci",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"dependencies": {
"next": "^12.3.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/core": "^7.19.0",
"@storybook/addon-actions": "^6.5.10",
"@storybook/addon-essentials": "^6.5.10",
"@storybook/addon-interactions": "^6.5.10",
"@storybook/addon-links": "^6.5.10",
"@storybook/addon-storyshots": "^6.5.10",
"@storybook/builder-webpack5": "^6.5.10",
"@storybook/manager-webpack5": "^6.5.10",
"@storybook/react": "^6.5.10",
"@storybook/testing-library": "^0.0.13",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"@types/react": "^18.0.19",
"babel-loader": "^8.2.5",
"jest": "^29.0.3",
"jest-environment-jsdom": "^29.0.3",
"typescript": "^4.8.3"
},
"resolutions": {
"react-test-renderer": "18.2.0"
}
}
↑react, typescript, jestなどのバージョンを少しアップデートしている。
mdx
に対するエラーは出るが、各storyに対して正常にsnapshotの作成と比較テストが実施出来る。
その他
StoryShotsの詳細なオプションは
などを参照のこと