Jestのスナップショットテストを導入してみる

🔰初心者です。

社内で運用しているアプリケーションがReact.jsを使っているので、Jestが用意しているスナップショットテストを導入することにしました！

##【導入のきっかけ】
現在進行形で既にあるアプリケーションのバグを対応することになりました。
ブラウザバックすると予期せぬコンポーネントでエラーになってしまい、結果画面真っ白になってしまうとか…
共通のヘッダーコンポーネントを修正しなければならなそうで、もし１行変えただけで全体に影響を及ぼしてしまったら…😭 スナップショットが入っていれば、１行対応した後にテストコマンド一つでスナップショットの差分が分かり、どこが変わったか一発で分かります！
スナップショットもGitで管理すると良いです☺️

##【まずは下準備】
必要なライブラリをインストールしておきます。

```shell
$ npm install -D babel-jest enzyme enzyme-adapter-react-16 react-test-renderer
```

importしているコンポーネントのパスがエイリアスになっているので、そこも解消するべくpackage.jsonに追記します。今回はエイリアス`@app/***/***`用に下のような記述をしています。

```json
{
  ...
  "jest": {
    "moduleNameMapper": {
      "^@app(.*)$": "<rootDir>/src/app$1"
    },
    "verbose": true,
    "setupFiles": [
      "<rootDir>/setupTests.js"
    ],
    "moduleFileExtensions": [
      "js",
      "json",
      "jsx"
    ]
  }
  ...
}
```

↑　参考：[Solve Module Import Aliasing for Webpack, Jest, and VSCode](https://medium.com/@justintulk/solve-module-import-aliasing-for-webpack-jest-and-vscode-74007ce4adc9)

`setupTests.js`の内容はこちらです！
enzymeの指定をしており、packge.jsonの`setupFiles`で読み込ませています。

```javascript
import Enzyme from 'enzyme'
import EnzymeAdapter from 'enzyme-adapter-react-16'

Enzyme.configure({
  adapter: new EnzymeAdapter(),
  disableLifecycleMethods: true,
})
```


##【いざ、実践！】
やり方は公式サイトを参考に始めます。

[公式 Snapshot Testing](https://jestjs.io/docs/ja/snapshot-testing)

今回実装するきっかけになったヘッダーコンポーネントから実装してみます！
公式サイトを見る限り簡単そう。👀
余裕でしょ！笑

一番最初に書いた記述がこちら！

###■失敗その１

```javascript
import React from 'react'
import renderer from 'react-test-renderer'
import { CommonHeader } from 'Components/Common/Header'

describe('<CommonHeader />', () => {
  it('スナップショット', () => {
    const tree = renderer
      .create(<CommonHeader />)
      .toJSON()
    expect(tree).toMatchSnapshot()
  })
})
```

これでは上手くいかず、こんなエラーが表示されました。

```shell
TypeError: this.props.handleFetchUser is not a function
```

コンポーネントでは、`handleFetchUser`を`mapDispatchToProps`で呼び出しています。
propsを渡せていないことによるエラーが他に１つ出ていたので、こちらを解消します！

###■失敗その２

```javascript
describe('<CommonHeader />', () => {
  it('スナップショット', () => {
    const props = {
      match: {},
      handleFetchUser: jest.fn()
    }
    const tree = renderer.create(<CommonHeader {...props} />).toJSON()
    expect(tree).toMatchSnapshot()
  })
})
```

エラーになっていたpropsを最初に指定してあげ、`<CommonHeader />`に渡してあげています。

ですが、今度はこんなエラーが出てしまいました！

```shell
Warning: Failed context type: The context `router` is marked as required in `Link`, but its value is `undefined`.
```

```shell
Invariant Violation: You should not use <Link> outside a <Router>
```

というわけで`<CommonHeader />`を`<Router />`で囲ってあげましょう！

###■ 完成形

```javascript
import { BrowserRouter as Router } from 'react-router-dom'

describe('<CommonHeader />', () => {
  it('スナップショット', () => {
    const props = {
      match: {},
      handleFetchUser: jest.fn()
    }
    const tree = renderer
      .create(
        <Router>
          <CommonHeader {...props} />
        </Router>
      )
      .toJSON()
    expect(tree).toMatchSnapshot()
  })
})
```

成功だ！と思ったら、`console.warn`の文字が…
全部緑色の方が清々しいので、このエラーについてググってみました。
対応策の中でこれかな…と思うものがあったのですが、今回は目をつぶり、保留とさせていただきます。😅

↓ 見当違いだったらごめんなさい。
[babel-jest does not transpile import/export in node_modules when Babel 7 is used #6229](https://github.com/facebook/jest/issues/6229#issuecomment-403539460)


##■ react-routerでpathの判定をしていた時

上記はpropsで渡していますが、[stach overflow](https://stackoverflow.com/questions/48895514/how-do-you-test-router-match-params-with-jest-and-enzyme)を見ていたら、下のような記述でも行けるようです。

```javascript
const matchURL = {
  params: {},
  isExact: true,
  path: "",
  url: ""
}
<CommonHeader match={matchURL} />
```

###余談

実はこのエラーの前に一つ、ヘッダーの中で非同期通信させています。
そこでenvファイルで管理している値を、`process.env`のように使っており、
最初は「process.env.DUMMY_NAMEがありません」と怒られました。
「なんじゃこりゃ！」と思いましたが、このプロジェクトの場合のみpackage.jsonでローカルと同じenvファイルを呼び出したらいけました😭


テストにパスしたので正常に「`__snapshots__/index.test.js.snap`」ファイルが生成されてくれました…っ！


###余談パート２

enzymeの`shallow`みたいな書き方もできることを知りました！
公式サイトを見ると、
react-test-rendererには、`ShallowRenderer`というのがに用意されているようです。（使ってみたら、上の記述とは違うsnapshotが吐き出されたので、今回は使用しませんでした。）

参考）[公式サイト Shallow Renderer](https://reactjs.org/docs/shallow-renderer.html)


##■ さらにさらに

「コンポーネントにsetStateさせた状態でスナップショット撮りたいなぁ…」を叶えてくれるライブラリが紹介されていたので、こちらを採用しました！
「[enzyme-to-json](https://www.npmjs.com/package/enzyme-to-json)」を使用しています。
enzymeの`shallow`を使って色々操作した後、`toJson`でwrapperを囲ってスナップショットをしてあげれば出来上がりです！

```javascript
expect(toJson(wrapper)).toMatchSnapshot();
```
試しに、setStateでタイトルを指定して、スナップショットの結果が正しく変わってくれるか検証してみました。

```javascript
import React from 'react'
import { shallow } from 'enzyme'
import renderer from 'react-test-renderer'
import toJson from 'enzyme-to-json'
import { CommonHeader } from 'Components/Common/Header'

const initialProps = {
  ...
}
const setup = (state = {}, props = initialProps) => {
  const wrapper = shallow(<CommonHeader {...props} />)
  return wrapper
}

describe('<CommonHeader />', () => {
  it('プロジェクト名がH1に表示される', () => {
    const wrapper = setup()
    wrapper.setState({
      projectName: 'ダミープロジェクト'
    })
    expect(wrapper.find('h1').text()).toEqual('ダミープロジェクト') // PASS
    expect(toJson(wrapper)).toMatchSnapshot()
  })
})
```

`enzyme-to-json`の参考になったサイトはこちらです。
・[6. Testing react components using Jest and Enzyme](https://medium.com/@aghh1504/6-testing-react-components-using-jest-and-enzyme-b85db96fa1e3)

スナップショットの結果もsetStateする前後でちゃんと変わってくれました！


##【参考にしたサイト】

・[React Component テストを書くための考え方](https://dev.classmethod.jp/etc/learn-react-component-test/#fn-350516-1)

・[What and How to Test with Jest and Enzyme. Full Instruction on React Components Testing](https://djangostars.com/blog/what-and-how-to-test-with-enzyme-and-jest-full-instruction-on-react-component-testing/)


###補足
スナップショットの記述を変えてテストを実行していくと、「上書きされた」とエラーが表示されます。
今はゴリゴリテストを実装していくフェーズなので、testの後に`-u`をつけて実行しましょう！

```shell
$ npm test -- -u
```

上書きされてテストがパスされていることが確認できます。


##【終えて】
参考にしたのはやはりYoutubeとUdemyです！（動画わかりやすくて感覚派の私には最高！笑）
さらに理解を深めたいので、他コンポーネントでもスナップショットテストを書いていきます。