スナップショットテスト
スナップショットのテストはUI が予期せず変更されていない かを確かめるのに非常に有用なツールです。
典型的なスナップショットテストでは、UIコンポーネントをレンダリングし、スナップショットを撮り、テストと一緒に保管されているスナップショットファイルと比較します。 2つのスナップショットが一致しない場合テストは失敗します: 予期されない変更があったか、参照するスナップショットが新しいバージョンのUIコンポーネントに更新される必要があるかのどちらかです。
Jestにおけるスナップショットテスト
React コンポーネントをテストする場合にも、同様のアプローチをとることができます。 アプリケーション全体の構築が必要となるグラフィカルなUIをレンダリングする代わりに、シリアライズ可能なReactツリーの値を素早く生成するテスト用レンダラーを利用できます。 Consider this example test for a Link component:
import renderer from 'react-test-renderer';
import Link from '../Link';
it('renders correctly', () => {
const tree = renderer
.create(<Link page="http://www.facebook.com">Facebook</Link>)
.toJSON();
expect(tree).toMatchSnapshot();
});
The first time this test is run, Jest creates a snapshot file that looks like this:
exports[`renders correctly 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;
生成されるスナップショットはコードの変更に追随し、かつコードレビューのプロセスの一部としてレビューされるべきです。 Jest uses pretty-format to make snapshots human-readable during code review. On subsequent test runs, Jest will compare the rendered output with the previous snapshot. それらが一致すれば、テストを通過します。 If they don't match, either the test runner found a bug in your code (in the <Link> component in this case) that should be fixed, or the implementation has changed and the snapshot needs to be updated.
The snapshot is directly scoped to the data you render – in our example the <Link> component with page prop passed to it. This implies that even if any other file has missing props (say, App.js) in the <Link> component, it will still pass the test as the test doesn't know the usage of <Link> component and it's scoped only to the Link.js. Also, rendering the same component with different props in other snapshot tests will not affect the first one, as the tests don't know about each other.
More information on how snapshot testing works and why we built it can be found on the release blog post. We recommend reading this blog post to get a good sense of when you should use snapshot testing. We also recommend watching this egghead video on Snapshot Testing with Jest.
スナップショットの更新
バグが混入した後でスナップショットテストが失敗したときは簡単に目星がつきます。 テストが失敗したら、その原因箇所に向かって問題を修正し、スナップショットテストが再びパスすることを確認すればよいのです。 ここで、意図的な仕様変更によりスナップショットテストが失敗するケースについて議論しましょう。
このような状況はたとえば以下の例のLinkコンポーネントが指すアドレスを意図的に変更した場合に起こります。
// Updated test case with a Link to a different address
it('renders correctly', () => {
const tree = renderer
.create(<Link page="http://www.instagram.com">Instagram</Link>)
.toJSON();
expect(tree).toMatchSnapshot();
});
このケースではJestは以下のような結果を出力します。

異なるアドレスを指すようにコンポーネントを更新したのですから、このコンポーネントのスナップショットに変更があると予想するのが妥当でしょう。 更新されたコンポーネントのスナップショットは今やこのテストで生成されたスナップショットと一致しないので、スナップショットのテストケースは失敗します。
これを解決するには、生成したスナップショットを更新する必要があります。 単純にスナップショットを再生成するように指示するフラグを付けてJestを実行するだけでできます。
jest --updateSnapshot
上記のコマンドを実行することで変更を受け入れることができます。 You may also use the equivalent single-character -u flag to re-generate snapshots if you prefer. このフラグは失敗する全てのスナップショットテストのスナップショットを再生成します。 意図しないバグにより追加されたスナップショットテストの失敗があれば、バグが混ざった状態でスナップショットを記録することを避 けるためにスナップショットを再生成する前にバグを修正する必要があります。
If you'd like to limit which snapshot test cases get re-generated, you can pass an additional --testNamePattern flag to re-record snapshots only for those tests that match the pattern.
You can try out this functionality by cloning the snapshot example, modifying the Link component, and running Jest.
インタラクティブ・スナップショットモード
失敗したスナップショットは、ウォッチモードで対話的に更新することもできます。

インタラクティブ・スナップショットモードに入ると、Jest は一度に1つのテストごとに、失敗したスナップショットをステップ実行させてくれます。 ここで、失敗した出力を確認できます。
ここで、スナップショットを更新するか、次にスキップするかを選択できます。

終了したら、Jest はウォッチモードに戻る前に概要を表示します。

インラインスナップショット
Inline snapshots behave identically to external snapshots (.snap files), except the snapshot values are written automatically back into the source code. つまり、外部ファイルに切り替えて正しい値が書き込まれていることを確認することなく、自動的に生成されたスナップショットの利点を得ることができます。
例:
First, you write a test, calling .toMatchInlineSnapshot() with no arguments:
it('renders correctly', () => {
const tree = renderer
.create(<Link page="https://example.com">Example Site</Link>)
.toJSON();
expect(tree).toMatchInlineSnapshot();
});
The next time you run Jest, tree will be evaluated, and a snapshot will be written as an argument to toMatchInlineSnapshot:
it('renders correctly', () => {
const tree = renderer
.create(<Link page="https://example.com">Example Site</Link>)
.toJSON();
expect(tree).toMatchInlineSnapshot(`
<a
className="normal"
href="https://example.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Example Site
</a>
`);
});
これだけです! You can even update the snapshots with --updateSnapshot or using the u key in --watch mode.
By default, Jest handles the writing of snapshots into your source code. However, if you're using prettier in your project, Jest will detect this and delegate the work to prettier instead (including honoring your configuration).
Property Matchers
Often there are fields in the object you want to snapshot which are generated (like IDs and Dates). If you try to snapshot these objects, they will force the snapshot to fail on every run:
it('will fail every time', () => {
const user = {
createdAt: new Date(),
id: Math.floor(Math.random() * 20),
name: 'LeBron James',
};
expect(user).toMatchSnapshot();
});
// Snapshot
exports[`will fail every time 1`] = `
{
"createdAt": 2018-05-19T23:36:09.816Z,
"id": 3,
"name": "LeBron James",
}
`;
For these cases, Jest allows providing an asymmetric matcher for any property. These matchers are checked before the snapshot is written or tested, and then saved to the snapshot file instead of the received value:
it('will check the matchers and pass', () => {
const user = {
createdAt: new Date(),
id: Math.floor(Math.random() * 20),
name: 'LeBron James',
};
expect(user).toMatchSnapshot({
createdAt: expect.any(Date),
id: expect.any(Number),
});
});
// Snapshot
exports[`will check the matchers and pass 1`] = `
{
"createdAt": Any<Date>,
"id": Any<Number>,
"name": "LeBron James",
}
`;
マッチャー以外の値はすべて正確にチェックされ、スナップショットに保存されます:
it('will check the values and pass', () => {
const user = {
createdAt: new Date(),
name: 'Bond... James Bond',
};
expect(user).toMatchSnapshot({
createdAt: expect.any(Date),
name: 'Bond... James Bond',
});
});
// Snapshot
exports[`will check the values and pass 1`] = `
{
"createdAt": Any<Date>,
"name": 'Bond... James Bond',
}
`;
If the case concerns a string not an object then you need to replace random part of that string on your own before testing the snapshot.
You can use for that e.g. replace() and regular expressions.
const randomNumber = Math.round(Math.random() * 100);
const stringWithRandomData = `<div id="${randomNumber}">Lorem ipsum</div>`;
const stringWithConstantData = stringWithRandomData.replace(/id="\d+"/, 123);
expect(stringWithConstantData).toMatchSnapshot();
Other ways this can be done is using the snapshot serializer or mocking the library responsible for generating the random part of the code you're snapshotting.
ベストプラクティス
スナップショットは、アプリケーション内で予期しないインターフェイスの変更を特定するための素晴らしいツールです。 UI、ログ、またはエラーメッセージのいずれであってもです。 あらゆるテスト戦略と同様に、知っておくべきベストプラクティスと、それらを効果的に使用するために、遵守すべきガイドラインがあります。