JavaScript
jest

この頃流行りのJestを導入して軽快にJSをテストしよう

Jestとは

JestはFacebook製のJSテストプラットフォームです。

近頃、急速にユーザーが増えているようで、ある調査では2017年にMochaやJasmineを超えてトップの使用率を獲得しているようです。


出展: https://ashleynolan.co.uk/blog/frontend-tooling-survey-2018-results

最近、自分のプロジェクトでもJasmine + KarmaからJestへ移行し、以下のようなメリットを得られました。

  • ブラウザの起動がないぶん軽快に動く
    • 実ブラウザ上ではなく、JSDOMのエミュレーション環境でテストが実行される
  • レポートが見やすい
    • テスト実行進捗がプログレスバーで表示され、Failしたテストの閲覧もしやすい
  • カバレッジを簡単に取得できる
    • 実行時にオプションをひとつ追加するだけ

この記事では、導入時にハマりどころがいくつかあったので、導入方法とともにハマったポイントと対処法を紹介します。

前提

以下バージョンを前提としています。

  • Jest 23.1.0

インストール

まず基本となる2つのパッケージをインストールします。

$ npm install --save-dev jest babel-jest

※ Babelを使っていない場合には babel-jest は不要ですが、多くのプロジェクトで使われていると思われるため、以下の説明ではBabelを使用していることを前提に書いています

TypeScriptファイルをテストしたい場合は、 加えて ts-jest をインストールしてください。

$ npm install --save-dev ts-jest

Vueファイルをテストしたい場合には、 加えて vue-jest をインストールしてください。

$ npm install --save-dev vue-jest

設定ファイル

jestの設定を定義します。

設定を定義する場所は以下の選択肢があります。

  1. 独立したファイルに定義する
  2. package.json に定義する

それぞれ、設定できる内容に違いはありませんので、どちらを選択しても問題ありません。

1. 独立したファイルに定義する場合の例

jest.config.js
module.exports = {
  verbose: true
};

2. package.jsonに定義する場合の例

package.json
{
  "name": "my-project",
  "jest": { // <-- jestプロパティ以下に設定を書く
    "verbose": true
  }
}

設定例

テストを始めるにあたって必要な設定を行います。

jest.config.jsファイルをプロジェクト直下に作成、もしくは、package.jsonファイルに追記します。

jest.config.js
module.exports = {
  transform: {
      '^.+\\.js$'  : '<rootDir>/node_modules/babel-jest',
      '.*\\.(ts)$' : '<rootDir>/node_modules/ts-jest',    // TypeScriptファイルをテストする場合
      '.*\\.(vue)$': '<rootDir>/node_modules/vue-jest'    // Vueファイルをテストする場合
  },
  moduleFileExtensions: ['js', 'ts', 'vue'] // テスト対象の拡張子を列挙する
}

上記のように定義することで、トランスパイルしたうえでテストを実行してくれるようになります。
このとき、 babel-jest.babelrcts-jest.tsconfig.json を参照しトランスパイルを行います。

import/export構文を使えない問題の対処

多くのブラウザアプリケーションではESModuleの import/export 構文を使用しているかと思います。
しかし、JestはNode.js上でテストを実行するため、Node.jsのモジュールシステムのCommonJSである必要があります。

(現時点のNode.js <= v10ではフラグを立て、ファイル拡張子の変更を行わないとESModuleに対応させることができません)

たとえば、下のような .babelrc の場合、ESModuleはCommonJSに変換されないため、このままJestを実行するとエラーとなります。

.babelrc
{
  "presets": [
    ["env", {
      "modules": false // <-- CommonJSに変換していない
    }]
  ]
}

そこで、テスト実行時のみ ESModuleをCommonJSに変換することで、Jestでもimport/export構文を使用できるように対応します。

まず、babelのpluginをインストールします。

$ npm install --save-dev babel-plugin-transform-es2015-modules-commonjs

test環境でのみpluginを有効とするように .babelrc に以下のように設定を追加します。

.babelrc
{
  "env": {
      "test": { // <-- NODE_ENV=testの場合のみpluginが有効になる
          "plugins": [
              "transform-es2015-modules-commonjs"
          ]
      }
  }
}

参考: https://github.com/facebook/jest/issues/2081

テストファイル

以下のsum.jsのテストを書いてみます。

sum.js
function sum(a, b) {
  return a + b;
}

export default sum;

Jestは*.test.jsもしくは*.spec.jsをテストファイルとして検出しテストを実行します。
また、__tests__ディレクトリ以下のすべてのファイルをテストファイルとして扱います。

ここではファイル名をsum.test.jsとしました。

sum.test.js
import sum from './sum';

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

テストの記法については他に詳しく解説してくれている記事があるので、ここでは説明を省きます。

自分は下の記事を参考にしました。

Jasmineやmochaなど、別のJSテストフレームワークを使った経験がある方なら、Jestでもほとんど同じ書き方ができるため、すぐに書き始められると思います。

テストの実行

npm-scriptsに、Jestを実行するコマンドを設定します。

package.json
"scripts": {
  "test": "NODE_ENV=test jest"
},

コマンドラインより実行

$ npm test

以下のようなレポートの出力を確認できたら成功です :tada:

カバレッジの取得

カバレッジの取得は、--coverageオプションを加えるだけで実現できます。

package.json
  "scripts": {
    "test": "NODE_ENV=test jest ./path/to/test_directory",
    "test-with-coverage": "npm test -- --coverage" // <-- カバレッジも取得する場合はこちらを実行
  },

コマンドラインより実行

$ npm run test-with-coverage

カバレッジのレポートが表示されます。

また、coverage/lcov-report/index.htmlを開くと詳細なレポートを参照できます。

ESLintの対応

ESLintを使っている場合、テストファイル中のtestexpectなどの記述がno-undefとしてエラーとなる場合があります。

これは、ESLintのJestプラグインを導入することで解決できます。

$ npm install --save-dev eslint-plugin-jest

.eslintrc に以下の設定を追加します。

.eslintrc
{
  "env": {
    "jest/globals": true
  },
  "plugins": [
    "jest"
  ],
}

テストファイルのマイグレーション

他のテストフレームワークからJestへ移行する場合、マイグレーションガイドを参考にすると良いでしょう。

ガイドではjest-codemodsというツールが紹介されています。
このツールを使うとテスト記法を自動でJestに対応するように変換してくれるようです。

Jasmineからの移行の場合、ほぼ互換性があるようで、自分のプロジェクトでは既存のテストファイルに何も手をいれずともJestで動いてくれました。

JSDOMに不足しているAPIをmockで補う

JestはNode.js上でブラウザ環境をエミュレートするために、JSDOMという実装を使っています。

このJSDOMはブラウザ環境のAPIを提供してくれているのですが、いくつか実装されていないAPIも存在するため、それらについてはmockで補う必要があります。

自分はテストを書くなかで、以下の不足しているAPIを見つけました。

  • Web Storage API(local storage, session storage)
  • window.getSelection

Web Storage APIはnpm packageでmockをインストールできます。

$ npm install --save-dev mock-local-storage

そして、インストールしたmockをimportするファイルを作成します。
さらに、window.getSelectionのmockもこのファイルに記述します。

jest-setup.js
import 'mock-local-storage';

// https://github.com/jsdom/jsdom/issues/937
window.getSelection = () => {
  return {
    addRange: () => {},
    removeAllRanges: () => {}
  };
};

作成したファイルをテスト起動時に読み込むように、以下の設定をjest.config.js(もしくはpackage.json)に追加します。

jest.config.js
module.exports = {
  setupTestFrameworkScriptFile: '<rootDir>/jest-setup.js' // <- 追加
}

このようにすることで、テスト起動時にmockを読み込ませ、エラーとなることを防ぐことができます。

おわりに

この記事で紹介した設定等はGithubに上げているので、よかったらこちらも見てやってください!
https://github.com/hogesuke/jest-demo