既存のReact+ReduxプロジェクトにJest+Enzymeを導入しようとして躓いた点


はじめに

フロントエンドのリファクタリングに伴い、Jest+Enzymeを用いたテストを導入することになった。
既存のプロジェクトは、React+Reduxで書かれており、複雑なWebpackの設定をしていたため、導入する際に躓いた点(発生したエラーと解決方法)をまとめる。

エラー発生時のpackage.json

react: 16.4.1
react-dom: 16.4.1
redux: 3.6.0
react-redux: 5.0.6
@babel/core: 7.0.0
webpack: 3.10.0
webpack-dev-server: 2.11.1

Jest + Enzymeの導入

基本的な流れ

基本的には以下の公式ドキュメント

に従って導入していった。
その後、既存のコンポーネントに対して、簡単なテスト(子コンポーネントが存在するか確認するだけ)が通せるように変更を加えた。

ドキュメントで苦戦したところ

基本的に書かれている通りに行えば上手くいった。
ドキュメント通りにやらなかったのでエラーが出た。

  • TypeError:Cannot read property 'current' of undefined

    • Getting Startedスナップショットテストで発生したエラー。
    • [原因] reactとreact-domのバージョンを16.9以上(ドキュメントでは最新にすべきだと書いてある)に上げる必要があるらしい。
    • [解決策] reactとreact-domのバージョンを上げれば動いた。

  • typeerror helpers(...).ensure is not a function

    • Getting Startedモック、Enzyme および React 16 を使用したスナップショットテスト(Enzyme)で発生したエラー。
    • [原因] @babel/coreのバージョンが低かった。
    • [解決策] @babel/coreのバージョンを上げれば動いた。

  • Enzyme Internal Error: Enzyme expects an adapter to be configured, but found none.

    • 同じく、Getting Startedモック、Enzyme および React 16 を使用したスナップショットテスト(Enzyme)で発生したエラー。
    • [原因] Enzymeを利用するには、Reactのバージョンに対応したアダプターをインストールする必要がある。
    • [解決策] 今回はReact16であるため、enzyme-adapter-react-16をインストールした。また、設定のためテストコードに以下を追記した。(参考: React v16でAirbnbのenzymeを使う時の覚書)
hogehoge.test.js
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });


既存のコンポーネントに対する簡単なテスト(子コンポーネントが存在するか確認するだけ)で躓いたところ

  • Cannot find module 'hogehoge' from 'fugafuga'
    • importあたりで躓いた。
    • [原因] Webpackでエイリアスを利用していて、その設定がJest側にないために失敗する。
    • [解決策] 以下のように、Jestの設定にWebpackのエイリアスと同じ設定をする必要がある。以下は「jest.config.js」を利用して設定を行う例だが、「package.json」に記載する方法もある。(参考:Using with webpack)
jest.config.js
module.exports = {
    moduleNameMapper: {
        // 「/src/components/**」に対し、「component」というエイリアスを設定する例
        "^component/(.*)": "<rootDir>/src/components/$1",
        // 「/src/containers/**」に対し、「container」というエイリアスを設定する例
        "^container/(.*)": "<rootDir>/src/containers/$1",
    },
};


npm install react-css-modules
jest.config.js
module.exports = {
    moduleNameMapper: {
        // 以下の行を追加(正規表現は適切なものに変える)
        "\\.(css|less|svg)$": "<rootDir>/node_modules/jest-css-modules",
    },


  • Store周りの設定でエラー

    • [原因] WebpackにStore周りの設定があり、その設定がJest側にないために失敗する。
    • [解決策] webpack.config.jsのpluginsに書かれていたDefinePluginの設定を、jest.config.jsのglobalsに転記した。

  • Invariant Violation: Could not find "store" in either the context or props of "Connect(Login)".

    • Reduxコンポーネントをテストする際に失敗する。
    • [原因] Jestのテストはアプリケーションから独立しており、storeは作成されないし、Reduxのセットアップもされないので、JestがReduxのstoreを探し出せずにエラーになる。
    • [解決策] 今回はインテグレーションテストを想定しているため、storeのモックを使ってテストを行う。redux-mock-storeをインストールし、テストコードに以下を追記。(参考: Reduxコンポーネントをユニットテストする方法 )
hogehoge.test.js
import configureStore from 'redux-mock-store'

// create any initial state needed
const initialState = {}; 
// here it is possible to pass in any middleware if needed into //configureStore
const mockStore = configureStore();
let wrapper;
let store;
beforeEach(() => {
    //creates the store with any initial state or middleware needed  
    store = mockStore(initialState);
    wrapper = shallow(<Login store={store}/>);
})

(引用: Reduxコンポーネントをユニットテストする方法)


  • SyntaxError: Unexpected token import > import Storage from 'react-native-storage';
    • react-native-storageをincludeする際にエラーになる。
    • [原因] react-native-storageはES6で書かれているため、トランスパイルが必要になる。Jestのデフォルト設定では、「node_modules/」配下はトランスパイルの対象外になっているので、トランスパイルされずにエラーとなる。(今回はreact-native-storageだけだが、react-native関連のパッケージは同様のエラーが起きる。)
    • [解決策] jest.config.jsに以下を追記し、node_modulesはトランスパイルの対象から外したまま、react-native-storageだけ対象に入れるように、デフォルトの設定を上書きする。(参考: Testing React Native Apps)
jest.config.js
    transformIgnorePatterns: [
        'node_modules/(?!(react-native-storage)/)'
    ],


  • console.warn node_modules/react-native-storage/storage.js:28 Data would be lost after reload cause there is no storageBackend specified!
    • ローカルストレージを設定する必要がある。
    • これはまだ手を付けてない。