テスト超初心者がスナップショットを実際に動かしてみた

## はじめに
テスト初心者の私が、公式ドキュメントを参照しながらスナップショットテストを実際に動かしてみました。そこで躓いたことや学んだことをまとめてみます。

なお、途中に入っているリンクはその項目を記載するにあたり参考にさせていただいたサイトです。ありがとうございます！

## スナップショットテストとは？
### スナップショットのそもそもの意味
- スナップショットとは、スナップ写真を意味する英単語
- 転じてスナップ写真のごとく「その瞬間を切り取る」ように、ある時点における状態を丸ごと保存したものを指す

https://clouddirect.jp.fujitsu.com/service/navi-words-snapshot#:~:text=%E3%82%B9%E3%83%8A%E3%83%83%E3%83%97%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%E3%81%A8%E3%81%AF%E3%80%81%E3%82%B9%E3%83%8A%E3%83%83%E3%83%97,%E3%82%82%E3%81%AE%E3%81%AE%E3%81%93%E3%81%A8%E3%82%92%E6%8C%87%E3%81%97%E3%81%BE%E3%81%99%E3%80%82


### Jestにおけるスナップショットテスト
- 保存されているスナップショットと比較し、意図しない変更が行われていないか検証する
- 初回テスト時に保存したスナップショットと現在の状態を比較し、差分があった場合にテストを失敗させる

### スナップショットテストを使うべきタイミング
以下サイトが非常に参考になりました！ありがとうございます。

https://zenn.dev/loglass/articles/595a91af94ff27

## 環境構築
テストを実行しようとした際にバグが発生し、困惑しましたが、以下のサイトを参考にすることでスムーズに環境構築ができました！ありがとうございます。

https://zenn.dev/crsc1206/articles/de79af226d0c69

package.jsonのscriptsを更新します。
```json
"scripts": {
    "test": "jest"
  }
```


## 実際に動かしてみます
Jestの公式サイトに書いてあるコード通りに動かしてみます。
まず、react-test-rendererをインストールします。

```
npm i react-test-renderer
```

https://www.npmjs.com/package/react-test-renderer

次に簡単なコンポーネントとそのテストファイルを作成します。
Jestの公式ドキュメントと同様にファイルを作成します。

https://jestjs.io/ja/docs/snapshot-testing#%E7%94%9F%E6%88%90%E3%81%95%E3%82%8C%E3%81%9F%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA%E3%81%A8%E5%87%A6%E7%90%86%E9%80%9F%E5%BA%A6%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E3%81%AE%E3%82%B9%E3%83%8A%E3%83%83%E3%83%97%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%E3%83%86%E3%82%B9%E3%83%88%E3%81%AE%E3%83%91%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%B3%E3%82%B9%E3%81%AF%E3%81%A9%E3%81%86%E3%81%A7%E3%81%99%E3%81%8B


Link.jsx
```jsx
const Link = ({page, children}) => <a href={page}>{children}</a>

export default Link
```

Link.test.js
```js
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();
});
```

それぞれコードを作成し、テストを実行します。
```
npm run test
```
すると__snapshots__ディレクトリが自動生成され、このような結果が保存されていました！

```
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders correctly 1`] = `
<a
  href="http://www.facebook.com"
>
  Facebook
</a>
`;
```

今後このファイルと比較して、意図しない変更があったら検知してくれるようです。

## もし差分があったら？
### 意図しない変更の場合
誤って`Link.jsx`を編集してしまったと仮定します。この場合、意図しない変更となるため、テストは失敗するのが正しい動作です。

```jsx
const Link = ({page, children}) => <a href={page}>{children}aaa</a> // 不要なaaaを追加

export default Link
```

#### 実行結果
意図しない変更が検出され、テストが失敗しました。
これにより、テストが正しく機能していることが確認できました。
```
✕ renders correctly (6 ms)

  ● renders correctly

    expect(received).toMatchSnapshot()

    Snapshot name: `renders correctly 1`

    - Snapshot  - 0
    + Received  + 1

      <a
        href="http://www.facebook.com"
      >
        Facebook
    +   aaa
      </a>

      5 |     .create(<Link page="http://www.facebook.com">Facebook</Link>)
      6 |     .toJSON();
    > 7 |   expect(tree).toMatchSnapshot();
        |                ^
      8 | });

      at Object.toMatchSnapshot (src/components/Link.test.js:7:16)

 › 1 snapshot failed.
Snapshot Summary
 › 1 snapshot failed from 1 test suite. Inspect your code changes or run `npm test -- -u` to update them.

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   1 failed, 1 total
Time:        0.779 s, estimated 1 s
```
### 意図した変更の場合
ここで、表示させたいリンクをFacebookからInstagramに変更したと仮定します。
テストもそれに合わせて変更する必要があるので、併せて変更します。

Link.test.js
```js
import renderer from 'react-test-renderer';
import Link from './Link';
it('renders correctly', () => {
  const tree = renderer
    .create(<Link page="http://www.instagram.com">Instagram</Link>)
    .toJSON();
  expect(tree).toMatchSnapshot();
});
```
これでテストが通ってくれるはず...!!と思ったら失敗していました。

```
 FAIL  src/components/Link.test.js
  ✕ renders correctly (6 ms)

  ● renders correctly

    expect(received).toMatchSnapshot()

    Snapshot name: `renders correctly 1`

    - Snapshot  - 2
    + Received  + 2

      <a
    -   href="http://www.facebook.com"
    +   href="http://www.instagram.com"
      >
    -   Facebook
    +   Instagram
      </a>

      5 |     .create(<Link page="http://www.instagram.com">Instagram</Link>)
      6 |     .toJSON();
    > 7 |   expect(tree).toMatchSnapshot();
        |                ^
      8 | });

      at Object.toMatchSnapshot (src/components/Link.test.js:7:16)

 › 1 snapshot failed.
Snapshot Summary
 › 1 snapshot failed from 1 test suite. Inspect your code changes or run `npm test -- -u` to update them.

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   1 failed, 1 total
Time:        0.794 s, estimated 1 s
```

#### 失敗した原因
スナップショットは、初回のテスト実行時に作成され、以降のテスト実行時に現在の状態と比較されます。そのため、スナップショットは1度保存されると、変更を加えても自動で情報が更新されません。

そのため、以下のコマンドで、手動で更新する必要があります。
```
npm test -- -u
```

#### 実行結果
```
 PASS  src/components/Link.test.js
  ✓ renders correctly (5 ms)

 › 1 snapshot updated.
Snapshot Summary
 › 1 snapshot updated from 1 test suite.

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 updated, 1 total
Time:        7.172 s
```

## 最新の推奨方法：Testing Libraryの活用
Jestの公式サイトに沿ってテストを行いましたが、調べているとReactの公式ドキュメントで以下のような記載を発見しました。

> react-test-renderer is deprecated. A warning will fire whenever calling ReactTestRenderer.create() or ReactShallowRender.render(). The react-test-renderer package will remain available on NPM but will not be maintained and may break with new React features or changes to React’s internals.

なんとReact公式では`react-test-renderer`は非推奨のようです。
代わりに`@testing-library/react`を使ってテストしてみましょう。

https://react.dev/warnings/react-test-renderer


### testing-library/reactを使ってテスト
コードを書き直して、更新し実行し直してみます。

Link.test.js
```js
import { render } from '@testing-library/react';
import Link from './Link';

it('renders correctly', () => {
  const { container } = render(<Link page="http://www.instagram.com">Instagram</Link>);
  expect(container).toMatchSnapshot();
});
```

#### 実行結果
問題なく成功しました！
```
 › 1 snapshot updated.
Snapshot Summary
 › 1 snapshot updated from 1 test suite.

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 updated, 1 total
Time:        1.098 s
```

## まとめ
- スナップショットは、意図しない変更を検知するのに便利
- テストを更新する際には、手動でスナップショットを更新する必要があるため注意が必要
- 最新のReact環境ではスナップショットのテストを行う際は、`react-test-renderer`ではなく、`@testing-library/react`を使用することが推奨されている