前回はクリックイベントのテストを行った。
今回は、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を使っていきたい。