前回はクリックイベントのテストを行った。
今回は、DOMが正しくレンダリングされているかのテストを行う。
正確には、DOMではなくて、仮想DOMであり、コンポーネントと言ったほうがいいのだと思う。
テストの対象となるコード
まずは、テストの対象となるコードをdom.js
として書く。
// dom.js import React from 'react'; const Profile = props=>{ const {data} = props; return( <div> 名前:{data.name}<br /> 年齢:{data.age} </div> ); }; export { Profile };
// dom-render.js import React from 'react'; import ReactDOM from 'react-dom'; import {Profile} from './dom.js'; const data = {name: 'Tom', age: 30}; ReactDOM.render(<Profile data={data} />, document.getElementById('mount'));
これで、dom-render.js
をビルドしてhtmlで読み込むと、ブラウザには次のように表示される。
名前:Tom 年齢:30
このコードをテストしていく。
テストを書く
準備として、enzyme-to-json
をインストールする。
npm install -S enzyme-to-json
テストコードは以下の内容。
// dom.test.js import React from 'react'; import { shallow } from 'enzyme'; import { shallowToJson } from 'enzyme-to-json'; import {Profile} from '../dom.js'; describe('domのテスト', ()=>{ test('Profileコンポーネントをレンダリングする', ()=>{ const elem = shallowToJson(shallow(<Profile data={{name: 'Tom', age: 30}} />)); expect(elem).toMatchSnapshot(); }); });
実行結果。
PASS __tests__/dom.test.js domのテスト ✓ Profileコンポーネントをレンダリングする (11ms) Snapshot Summary › 1 snapshot written in 1 test suite. Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 1 added, 1 total Time: 5.621s Ran all test suites matching "__tests__/dom.test.js".
後述するが、toMatchSnapshot()
のテストは、シンタックスエラーなどが無い限り、一度目は必ずパスする仕組みになっている。
shallowToJson()
対象となるコンポーネントをshallow
でレンダリングし、その戻り値をshallowToJson
の引数として渡すことで、そのレンダリングの内容をテストできるようになる。
なお、shallow
とshallowToJson
ではなく、mount
とmountToJson
でも、同じことが出来る。
toMatchSnapshot()
expect(shallowToJsonの戻り値).toMatchSnapshot();
このようにすることで、shallow
でレンダリングした内容をテストできる。
スナップショットの仕組み
toMatchSnapshot()
を実行すると、テストファイルの同階層に__snapshots__
というフォルダが作られ、そのなかに<テスト実行ファイル名>.snap
が作られる。今回の例だと、dom.test.js.snap
。
以下が、その中身。
// dom.test.js.snap exports[`domのテスト Profileコンポーネントをレンダリングする 1`] = ` <div> 名前: Tom <br /> 年齢: 30 </div> `;
初回テスト時や、対応するスナップショットがない場合は、このようにスナップショットが作成される。そしてその際、テストは必ずパスする。
2回目以降のテスト、つまり既にスナップショットが存在する場合は、今回のレンダリングの内容と、保存してあるスナップショットとを比較して、テストする。
スナップショットの中身を変更してみる。
exports[`domのテスト Profileコンポーネントをレンダリングする 1`] = ` <div> 名前: Bob <br /> 年齢: 45 </div> `;
こうすると、意図通り、テストはパスしなくなる。
そこで、パスするよう、テストを書き換えていく。
import React from 'react'; import { shallow } from 'enzyme'; import { shallowToJson } from 'enzyme-to-json'; import {Profile} from '../dom.js'; describe('domのテスト', ()=>{ test('Profileコンポーネントをレンダリングする', ()=>{ const elem = shallowToJson(shallow(<Profile data={{name: 'Bob', age: 45}} />)); expect(elem).toMatchSnapshot(); }); });
このような手順を経ることで、意図した通りにコンポーネントがレンダリングされているかをテストすることが出来る。
スナップショットファイルの中身
exports[describeの名前 testの名前 番号`] = ` レンダリングの内容 `;
番号は連番になっておりtest
の中でtoMatchSnapshot()
が呼ばれる度に番号が振られていく。
スナップショットファイルの再構築
テスト実行時に-- -u
というオプションを付けることで、今回のテストの内容でスナップショットを全て書き換えてくれる。
クリックイベントと組み合わせる
応用編として、前回学んだクリックイベントのテストと組み合わせてみる。
まず、テストの対象となるコードを書く。
// dom2.js import React from 'react'; class Count extends React.Component{ constructor(props){ super(props); this.state = {counter:0}; }; render(){ return( <div> <div>{this.state.counter}</div> <button onClick={()=>{ this.setState({counter: this.state.counter+1}); }}>Click!</button> </div> ); }; }; export { Count };
// dom2-render.js import React from 'react'; import ReactDOM from 'react-dom'; import {Count} from './dom2.js'; ReactDOM.render(<Count />, document.getElementById('mount'));
こうすると画面には数値(初期値は0)とボタンが表示され、ボタンをクリックする度に数値の数が増えていく。
このコードを、テストしていく。
import React from 'react'; import { mount } from 'enzyme'; import { mountToJson } from 'enzyme-to-json'; import { Count } from '../dom2.js'; describe('domのテスト', () => { test('Countのテスト', () => { const elem = mount(<Count />); let currentDom = mountToJson(elem); expect(currentDom).toMatchSnapshot(); }); });
上記のコードを書くと、以下のスナップショットが作成される。
exports[`domのテスト Countのテスト 1`] = ` <Count> <div> <div /> <button onClick={[Function]}> Click! </button> </div> </Count> `;
<div>0</div>
となるべきところが、<div />
となっている。0は省略されるようだ。
このスナップショットを次のように書き換える。
exports[`domのテスト Countのテスト 1`] = ` <Count> <div> <div> 1 </div> <button onClick={[Function]}> Click! </button> </div> </Count> `; exports[`domのテスト Countのテスト 2`] = ` <Count> <div> <div> 2 </div> <button onClick={[Function]}> Click! </button> </div> </Count> `;
クリックする度に数値が増えることをテストしている。先程のテストコードを実行すると、当然、通らない。
そこで、テストを以下のように書き換える。
import React from 'react'; import { mount } from 'enzyme'; import { mountToJson } from 'enzyme-to-json'; import { Count } from '../dom2.js'; describe('domのテスト', () => { test('Countのテスト', () => { const elem = mount(<Count />); elem.find('button').simulate('click'); let currentDom = mountToJson(elem); expect(currentDom).toMatchSnapshot(); elem.find('button').simulate('click'); currentDom = mountToJson(elem); expect(currentDom).toMatchSnapshot(); }); });
toMatchSnapshot()
を2回行っているが、それぞれ、スナップショットと一致し、テストはパスする。
上記の結果から、テストのなかでレンダリングしたコンポーネントに対してイベントを起こせること、それによる表示の変更も問題なく行われていることが、確認できる。
また、mountToJson()
やtoMatchSnapshot()
を使うことで、その変化をテストできることが分かった。
イベントやDOMのテストは苦手だったのだが、それが出来るようになったことで、かなり視界が晴れた感じがする。
実際に使うことでこそ身に付くので、今後の開発で積極的にJestを使っていきたい。