10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-03-05

🔰初心者です。

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

【導入のきっかけ】

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

【まずは下準備】

必要なライブラリをインストールしておきます。

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

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

{
  ...
  "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

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

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

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

【いざ、実践!】

やり方は公式サイトを参考に始めます。

公式 Snapshot Testing

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

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

■失敗その1

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()
  })
})

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

TypeError: this.props.handleFetchUser is not a function

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

■失敗その2

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

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

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

Warning: Failed context type: The context `router` is marked as required in `Link`, but its value is `undefined`.
Invariant Violation: You should not use <Link> outside a <Router>

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

■ 完成形

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

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

上記はpropsで渡していますが、stach overflowを見ていたら、下のような記述でも行けるようです。

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」ファイルが生成されてくれました…っ!

余談パート2

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

参考)公式サイト Shallow Renderer

■ さらにさらに

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

expect(toJson(wrapper)).toMatchSnapshot();

試しに、setStateでタイトルを指定して、スナップショットの結果が正しく変わってくれるか検証してみました。

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

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

【参考にしたサイト】

React Component テストを書くための考え方

What and How to Test with Jest and Enzyme. Full Instruction on React Components Testing

補足

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

$ npm test -- -u

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

【終えて】

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

10
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?