enzyme と react-test-renderer と @testing-library/react を素朴に試す
React のテスト用ライブラリっていくつかある上に、それぞれサポートしている機能が違うんですよね。 ついでに提供されているメソッドや、メソッドチェーンの様式も様々なので、出来るだけ速やかに繰り返し実行できる環境で触りながら把握できると便利です。 Jest でコンソール出力しながらだとまどろっこしいので、手元でサクサク試せる環境をセットアップした時のメモです。
React のテスティングライブラリ
今回対象にするのは以下の3つのライブラリです。現時点の理解を書き添えてみました。
- enzyme
- react-test-renderer
- スナップショットを取ることができる
- @testing-library/react
fireEvent
でイベントを発火することができる
セットアップ
package.json
の抜粋です。エラーを消すために色々追加していったら、こんな感じで落ち着きました。
{ "dependencies": { "@testing-library/react": "^8.0.7", "enzyme": "^3.10.0", "enzyme-adapter-react-16": "^1.14.0", "jsdom": "^15.1.1", "jsdom-global": "^3.0.2", "nodemon": "^1.19.1", "react": "^16.8.6", "react-dom": "^16.8.6", "react-test-renderer": "^16.8.6" } }
nodemon
はファイルを監視させて再実行させるために利用しています。お試し環境の良き友ですね。
今回は省けるだけ環境構築を省く、という方針でセットアップしたので、JSX は利用できなくなっています。
小規模なら React.createElement
を直接利用してもそこまで大変ではありませんでした。
それぞれ試していく
テスト対象のコンポーネント
イベントや多少のネストなんかを確認できるようにしてみました。
const { createElement: e, useState } = require('react') // Sample Components // ----------------- const CountButton = () => { const [count, setCount] = useState(0) return e( 'button', { onClick: () => setCount(c => c + 1) }, `count: ${count}` ) } const C1 = props => e('div', {}, 'C1: ', props.content) const C2 = () => e('div', {}, e(C1), e(C1, { content: 'content-from-C2' }), e('div', {}, e(CountButton), e(CountButton), e('div', {}, e(C1) ) ) ) module.exports = { CountButton, C1, C2 }
enzyme
const { createElement: e } = require('react') const enzyme = require('enzyme') const Adapter = require('enzyme-adapter-react-16') const { shallow } = enzyme const { C1, C2 } = require('./components') enzyme.configure({ adapter: new Adapter() }) console.log(shallow(e(C2)).debug()) console.log(shallow(e(C2)).find(C1).at(1).dive().debug())
これを実行すると
<div> <C1 /> <C1 content="content-from-C2" /> <div> <CountButton /> <CountButton /> <div> <C1 /> </div> </div> </div> <div> C1: content-from-C2 </div>
と出力されます。enzyme の shallow
ってこういうことなんだなとか、
この段階ではこういうオブジェクトが帰ってきているんだなとかを眺めるための環境です。
試しに C1
内でエラーを投げるようにしてみたのですが、問題なく同じ出力がされました。
react-test-renderer
const { createElement: e, useState } = require('react') const { create } = require('react-test-renderer') const { C1, C2 } = require('./components') console.log(create(e(C2)).toTree()) console.log(create(e(C2)).toJSON())
これを実行すると
{ nodeType: 'component', type: [Function: C2], props: {}, instance: null, rendered: { nodeType: 'host', type: 'div', props: { children: [Array] }, instance: null, rendered: [ [Object], [Object], [Object] ] } } { type: 'div', props: {}, children: [ { type: 'div', props: {}, children: [Array] }, { type: 'div', props: {}, children: [Array] }, { type: 'div', props: {}, children: [Array] } ] }
@testing-library/react
動作させるには jsdom-global
を実行しておくことが必要です。
Jest と組み合わせて使う場合には不要です。
const { createElement: e } = require('react') const { render, fireEvent } = require('@testing-library/react') require('jsdom-global')() const { C2 } = require('./components') const { getAllByText, debug } = render(e(C2)) debug() fireEvent.click(getAllByText(/count/)[0]) debug()
以下のように出力されます。shallow
ではないので enzyme
の表示に比べて長いですね。
<body> <div> <div> <div> C1: </div> <div> C1: content-from-C2 </div> <div> <button> count: 0 </button> <button> count: 0 </button> <div> <div> C1: </div> </div> </div> </div> </div> </body> <body> <div> <div> <div> C1: </div> <div> C1: content-from-C2 </div> <div> <button> count: 1 </button> <button> count: 0 </button> <div> <div> C1: </div> </div> </div> </div> </div> </body>
テスティングライブラリは大量のメソッド・ユーティリティが存在するので、TypeScript で構築すれば良かったです。