The future starts today

Webとか英語とか育児とかに関する雑記

Jestのスナップショット機能を使ってみた

f:id:shibe97:20170320232906j:plain

Jestとは

facebook.github.io

  • Facebook製のテストフレームワーク
  • Reactアプリケーションのテストに向いてる
  • スナップショットテストがある

スナップショットテストとは

  • スナップショット != スクリーンショット
  • 画像の差分を取るのではない
  • 出力されるHTMLの構造を比較する

Worcに導入してみた

shibe97.hatenablog.com

(別件ですが、先日Windowsにも対応しました!)

Worcはかなり細かくコンポーネントを分けているので、試しやすかった。
完全に自分にとっての実験環境になりつつある・・・

対象のコンポーネント

とりあえず今回は、ツイート部分のテキストエリアコンポーネントを対象としてテストを行なった。
以下のようなコンポーネント

  1 import React from 'react';
  2 import styles from './textarea.css';
  3
  4 export default ({ value = '', inputUpdate }) => (
  5   <textarea
  6     className={styles.textarea}
  7     placeholder="What's happening?"
  8     value={value}
  9     onChange={e => inputUpdate(e.target.value)}
10   />
11 );

スナップショット機能の使い方

スナップショット機能を使うにはreact-test-rendererというパッケージが必要。

$ npm i -D jest react-test-renderer

今回は対象のコンポーネントと同階層にテストファイルも置いた。
テストファイルの中身は以下のような感じ。

  1 import React from 'react';
  2 import renderer from 'react-test-renderer';
  3
  4 import Textarea from './Textarea';
  5
  6 describe('components/atoms/textarea', () => {
  7   it('renders correctly', () => {
  8     const tree = renderer.create(
  9       <Textarea value="test" inputUpdate={() => {}} />
10     ).toJSON();
11     expect(tree).toMatchSnapshot();
12   });
13 });

スナップショットが生成される場所

テストを実行すると、テストファイルが置いてある階層に__snapshots__ディレクトリが生成される。
このディレクトリ内にスナップショットファイルが入る。

.
├── Textarea.js
├── Textarea.test.js
├── __snapshots__
│   └── Textarea.test.js.snap
└── textarea.css

生成されたスナップショット

次のようなスナップショットファイルが生成される。

exports[`components/atoms/textarea renders correctly 1`] = `
<textarea
  className="textarea"
  onChange={[Function]}
  placeholder="What\'s happening?"
  value="test" />
`;

テスト時にこのファイルと常に比較され、差分があるとこける。

そのままでは修正するたびにこけてしまうし、意図的な変更をする場合でもテストがこけてしまう。

そこでスナップショットを更新するオプションが用意されている。

$ npm test -- -u

ちなみに--はエスケープの役割だそう。 npm testjestが呼ばれるようにpackage.jsonscriptsに設定してある。

試しに少し変更を入れてテストを走らせてみる

valueの部分にtestという文字列を付け加える。

  1 import React from 'react';
  2 import styles from './textarea.css';
  3
  4 export default ({ value = '', inputUpdate }) => (
  5   <textarea
  6     className={styles.textarea}
  7     placeholder="What's happening?"
  8     value={`${value}test`}
  9     onChange={e => inputUpdate(e.target.value)}
10   />
11 );

この状態でテストを走らせると次のようになる。

f:id:shibe97:20170321215237p:plain

差分がとても分かりやすく表示されている。
オプションを付けてスナップショットを更新するとエラーはなくなる。

Worcでの運用方法

とりあえず以下のようにしてみた。けどもっと良い運用方法あるはず。
皆どうしているんだろうか。

  • 元々push時にCIでnpm testを回していた
  • しかし、CI上でnpm test -- -uしてしまうとスナップショットテスト全て通過してしまうので意味がなくなってしまう
  • そこで、push直前にnpm testして、うまくいった場合にCI上でnpm test -- -uを行う
  • push直前の処理はGit hooksを利用

Git hooksとは

Gitコマンドにフックして何らかの処理を行わせる仕組み。

.git/hooks以下のシェルファイルをいじると動く。

  • pre-commit
  • post-commit
  • pre-push
  • post-push
  • pre-receive
  • post-receive
  • etc…

今回自分はpre-pushを利用。

.git以下はGitHubで共有できないので、postinstall時などに書き換えるようなスクリプトを用意する必要がある

まとめ

スナップショットという単語に興味は持っていたけどずっと触れていなくて、ついに試すことができて良かった。 非常に簡単にテスト実行まで試すことができたので、誰でもさくっと導入できると思う。。

難しいと思うのは運用面で、複数個所がエラーで落ちた際に、本当は正解なのに前回のスナップショットを正としているためエラーとなってしまっているパターンが混ざっていると、その切り分けをしなくてはならないのが大変だと思った。

このあたりはよく考えれば良い解決方法がありそうなので、色々と試していきたい。