🔰初心者です。
社内で運用しているアプリケーションがReact.jsを使っているので、Jestが用意しているスナップショットテストを導入することにしました!
【導入のきっかけ】
現在進行形で既にあるアプリケーションのバグを対応することになりました。
ブラウザバックすると予期せぬコンポーネントでエラーになってしまい、結果画面真っ白になってしまうとか…
共通のヘッダーコンポーネントを修正しなければならなそうで、もし1行変えただけで全体に影響を及ぼしてしまったら…😭 スナップショットが入っていれば、1行対応した後にテストコマンド一つでスナップショットの差分が分かり、どこが変わったか一発で分かります!
スナップショットもGitで管理すると良いです☺️
【まずは下準備】
必要なライブラリをインストールしておきます。
$ npm install -D babel-jest enzyme enzyme-adapter-react-16 react-test-renderer
importしているコンポーネントのパスがエイリアスになっているので、そこも解消するべくpackage.jsonに追記します。今回はエイリアス@app/***/***
用に下のような記述をしています。
{
...
"jest": {
"moduleNameMapper": {
"^@app(.*)$": "<rootDir>/src/app$1"
},
"verbose": true,
"setupFiles": [
"<rootDir>/setupTests.js"
],
"moduleFileExtensions": [
"js",
"json",
"jsx"
]
}
...
}
↑ 参考:Solve Module Import Aliasing for Webpack, Jest, and VSCode
setupTests.js
の内容はこちらです!
enzymeの指定をしており、packge.jsonのsetupFiles
で読み込ませています。
import Enzyme from 'enzyme'
import EnzymeAdapter from 'enzyme-adapter-react-16'
Enzyme.configure({
adapter: new EnzymeAdapter(),
disableLifecycleMethods: true,
})
【いざ、実践!】
やり方は公式サイトを参考に始めます。
今回実装するきっかけになったヘッダーコンポーネントから実装してみます!
公式サイトを見る限り簡単そう。👀
余裕でしょ!笑
一番最初に書いた記述がこちら!
■失敗その1
import React from 'react'
import renderer from 'react-test-renderer'
import { CommonHeader } from 'Components/Common/Header'
describe('<CommonHeader />', () => {
it('スナップショット', () => {
const tree = renderer
.create(<CommonHeader />)
.toJSON()
expect(tree).toMatchSnapshot()
})
})
これでは上手くいかず、こんなエラーが表示されました。
TypeError: this.props.handleFetchUser is not a function
コンポーネントでは、handleFetchUser
をmapDispatchToProps
で呼び出しています。
propsを渡せていないことによるエラーが他に1つ出ていたので、こちらを解消します!
■失敗その2
describe('<CommonHeader />', () => {
it('スナップショット', () => {
const props = {
match: {},
handleFetchUser: jest.fn()
}
const tree = renderer.create(<CommonHeader {...props} />).toJSON()
expect(tree).toMatchSnapshot()
})
})
エラーになっていたpropsを最初に指定してあげ、<CommonHeader />
に渡してあげています。
ですが、今度はこんなエラーが出てしまいました!
Warning: Failed context type: The context `router` is marked as required in `Link`, but its value is `undefined`.
Invariant Violation: You should not use <Link> outside a <Router>
というわけで<CommonHeader />
を<Router />
で囲ってあげましょう!
■ 完成形
import { BrowserRouter as Router } from 'react-router-dom'
describe('<CommonHeader />', () => {
it('スナップショット', () => {
const props = {
match: {},
handleFetchUser: jest.fn()
}
const tree = renderer
.create(
<Router>
<CommonHeader {...props} />
</Router>
)
.toJSON()
expect(tree).toMatchSnapshot()
})
})
成功だ!と思ったら、console.warn
の文字が…
全部緑色の方が清々しいので、このエラーについてググってみました。
対応策の中でこれかな…と思うものがあったのですが、今回は目をつぶり、保留とさせていただきます。😅
↓ 見当違いだったらごめんなさい。
babel-jest does not transpile import/export in node_modules when Babel 7 is used #6229
■ react-routerでpathの判定をしていた時
上記はpropsで渡していますが、stach overflowを見ていたら、下のような記述でも行けるようです。
const matchURL = {
params: {},
isExact: true,
path: "",
url: ""
}
<CommonHeader match={matchURL} />
余談
実はこのエラーの前に一つ、ヘッダーの中で非同期通信させています。
そこでenvファイルで管理している値を、process.env
のように使っており、
最初は「process.env.DUMMY_NAMEがありません」と怒られました。
「なんじゃこりゃ!」と思いましたが、このプロジェクトの場合のみpackage.jsonでローカルと同じenvファイルを呼び出したらいけました😭
テストにパスしたので正常に「__snapshots__/index.test.js.snap
」ファイルが生成されてくれました…っ!
余談パート2
enzymeのshallow
みたいな書き方もできることを知りました!
公式サイトを見ると、
react-test-rendererには、ShallowRenderer
というのがに用意されているようです。(使ってみたら、上の記述とは違うsnapshotが吐き出されたので、今回は使用しませんでした。)
■ さらにさらに
「コンポーネントにsetStateさせた状態でスナップショット撮りたいなぁ…」を叶えてくれるライブラリが紹介されていたので、こちらを採用しました!
「enzyme-to-json」を使用しています。
enzymeのshallow
を使って色々操作した後、toJson
でwrapperを囲ってスナップショットをしてあげれば出来上がりです!
expect(toJson(wrapper)).toMatchSnapshot();
試しに、setStateでタイトルを指定して、スナップショットの結果が正しく変わってくれるか検証してみました。
import React from 'react'
import { shallow } from 'enzyme'
import renderer from 'react-test-renderer'
import toJson from 'enzyme-to-json'
import { CommonHeader } from 'Components/Common/Header'
const initialProps = {
...
}
const setup = (state = {}, props = initialProps) => {
const wrapper = shallow(<CommonHeader {...props} />)
return wrapper
}
describe('<CommonHeader />', () => {
it('プロジェクト名がH1に表示される', () => {
const wrapper = setup()
wrapper.setState({
projectName: 'ダミープロジェクト'
})
expect(wrapper.find('h1').text()).toEqual('ダミープロジェクト') // PASS
expect(toJson(wrapper)).toMatchSnapshot()
})
})
enzyme-to-json
の参考になったサイトはこちらです。
・6. Testing react components using Jest and Enzyme
スナップショットの結果もsetStateする前後でちゃんと変わってくれました!
【参考にしたサイト】
・What and How to Test with Jest and Enzyme. Full Instruction on React Components Testing
補足
スナップショットの記述を変えてテストを実行していくと、「上書きされた」とエラーが表示されます。
今はゴリゴリテストを実装していくフェーズなので、testの後に-u
をつけて実行しましょう!
$ npm test -- -u
上書きされてテストがパスされていることが確認できます。
【終えて】
参考にしたのはやはりYoutubeとUdemyです!(動画わかりやすくて感覚派の私には最高!笑)
さらに理解を深めたいので、他コンポーネントでもスナップショットテストを書いていきます。