Jest の使い方

近年の Web 開発では、サーバーサイド(PHP など)ではなく、クライアントサイド(JavaScript)で処理することが多くなりました。 JavaScript の重要性が増えるに伴って、そのテストもまた重要になってきます。

Jest とは?

jest_thumb.png

Jest は、Meta 社(旧:Facebook 社)製の JavaScript 用のテストフレームワークです。 シンプルさを重視して作成されており、簡単にテストを書くことができます。 シンプルながらも機能は豊富で、JavaScript でよく使われる非同期コードのテストや、コードカバレッジ解析なども簡単に行うことができます。

Jest のインストール

さっそくですがインストールしていきましょう。

npm でインストールする

npm を使ってインストールするには、下記のコマンドを実行するだけです。 基本的には開発環境で使うツールだと思いますので、--save-dev オプションを付けておきましょう。 npm の使い方がわからないという方は、まずは「Node.js と npm の使い方(その1:概要)」を参考にしてください。

npm install --save-dev jest

インストールできたか確認する

Jest をインストールすると、jest コマンドが使えるようになっています。 下記のコマンドを実行してバージョン番号が表示されれば、ちゃんとインストールできています。

jest --version

ヘルプを表示する

jest コマンドの詳しい使い方が知りたい場合は、ヘルプを表示してみましょう。

jest --help

テストの書き方

テストを書くにはいくつかルールがあります。

  • テストコードとして認識されるファイルは下記です(設定で変更可能です)。
    • "__tests__" ディレクトリの中のファイル
    • 拡張子に ".spec" もしくは ".test" が含まれているファイル(例:sum.test.js)
  • テストの内容は、test() を使用して記述します。
    it() は、test() のエイリアス(別名)として使用できます。
  • expect() と Matcher を使用して、期待される値と実際の値が等しいことを確かめます。
JavaScript ファイル
const sum = require('./sum.js');

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

expect() と Matcher

expect()

expect() は値をテストしたい時に毎回使用する関数です。 引数には、判定したい実際の値を設定します。 expect() は、Matcher と組み合わせて使用します。

Matcher

Matcher(マッチャー)は、テストしたいことが正しいかどうかを判定するための関数です。 上記の例では、.toBe() の部分が Matcher になります。 引数には、期待する値を設定します。

Matcher にどんな関数があるかは、ここでは記載しません。 大量にありますので、公式のドキュメントを見てもらった方が良いと思います。

テストのグループ化

describe() を使用すると、いくつかの関連するテストをまとめたブロックを作成できます。 テストをグループにまとめたい場合に便利です。 describe() はネストすることができます。

JavaScript ファイル
const sum = require('./sum.js');

describe('sum', () => {

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

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

リピーティングセットアップ

各テストが実行される際、その前処理と後処理を共通化したい場合、Jest では beforeEach()afterEach() という関数が用意されています。 この関数で前処理と後処理を定義すると、各テストが呼ばれる際に自動的に呼んでくれます。 beforeEach() が前処理、afterEach() が後処理です。

JavaScript ファイル
const sum = require('./sum.js');

beforeEach(() => {
	// テストごとの前処理
});

afterEach(() => {
	// テストごとの後処理
});

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

ワンタイムセットアップ

各ファイルで1回だけ前処理と後処理を行いたい場合、Jest では beforeAll()afterAll() という関数が用意されています。 この関数で前処理と後処理を定義すると、各ファイルで1回だけ自動的に呼んでくれます。 beforeAll() が前処理、afterAll() が後処理です。

JavaScript ファイル
const sum = require('./sum.js');

beforeAll(() => {
	// ファイルで1回だけの前処理
});

afterAll(() => {
	// ファイルで1回だけの後処理
});

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

基本的な使い方

さっそく使ってみましょう。

全てのテストを実行する

jest コマンドを実行すると全てのテストが実行されます。 オプションなどを指定する必要はありません。

jest

特定のテストのみを実行する

特定のテストのみを実行する場合は、jest コマンドの後にファイル名の正規表現を指定します。 正規表現に一致するファイルだけが選択され、テストが実行されます。

例:"tests/js/model" ディレクトリ配下のテストのみ実行する場合
jest tests/js/model/

特定のテスト名に一致するテストのみを実行する

テスト名というのは、テストを記述する際に describe()test()it() の第一引数で指定した文字列のことです。

特定のテスト名に一致するテストのみを実行する場合は、jest コマンドに、--testNamePattern オプション指定します。 --testNamePattern オプション には、エイリアス(別名) -t オプションがあります。 テスト名は正規表現で指定します。

例:テスト名が "str" で始まるテストのみ実行する場合
jest -t ^str

変更があったテストのみを実行する

Git もしくは Mercurial と連携し、変更があったテストのみを実行します。 Git もしくは Mercurial が使用できない場合は実行できません。

変更があったテストのみを実行する場合は、jest コマンドに、--onlyChanged オプション指定します。 --onlyChanged オプション には、エイリアス(別名) -o オプションがあります。

jest -o

ファイルの変更を監視し、自動的にテストを実行する

ファイルを変更するたびにテストを実行するのはとても面倒です。 そこで、ファイルの変更を Jest が監視し、ファイルに変更があった場合に自動でテストを実行してくれるようになるオプション --watch--watchAll があります。 このオプションを付けて Jest を実行すると、ファイルに何か変更があった場合に、自動でテストが実行されるようになります。

--watch オプションは、Git もしくは Mercurial と連携し、変更があったテストのみを実行します。 Git もしくは Mercurial が使用できない場合は実行できません。

--watchAll オプションは、全てのテストを実行します。

監視をやめる場合は、Ctrl + C を押せば終了できます。

変更があったテストのみを実行する場合
jest --watch
全てのテストを実行する場合
jest --watchAll

設定ファイル

Jest は、デフォルトでうまく動作すること(ゼロコンフィグ)を目指して作られていますが、 環境によっては細かい設定が必要になることもあると思います。 コマンドのパラメーターだけでも細かな設定ができるのですが、さすがに全てを指定するのは大変です。 設定ファイルを使用した方が良いでしょう。

設定ファイルのフォーマットとして JavaScript(CommonJS もしくは ES Modules)、TypeScript、JSON が使用できます。 そのため、これらの中からお好きなフォーマットを選んで作成することができます。

設定ファイルを使用する

設定ファイルを使用する方法は3つあります。

デフォルトの設定ファイルを使用する

1つ目の方法は Jest のデフォルトの設定ファイルを使用する方法です。 オプションを指定する必要はありません。

Jest は、デフォルトで現在のディレクトリから設定ファイルを探します。 この時に探されるファイルの名前は、「jest.config.js」「jest.config.ts」「jest.config.mjs」「jest.config.cjs」「jest.config.json」です。 このファイル名のどれかと一致するファイルが見つかった場合に、そのファイルが自動的に読み込まれます。

jest

--config オプションで指定する

2つ目の方法は、--config オプションで使用したい設定ファイルを指定することです。 複数の設定ファイルを使用して、設定を切り替えたい場合などには便利かもしれません。 --config オプション には、エイリアス(別名) -c オプションがあります。

例:設定ファイルとして「config.json」を指定する場合
jest --config=config.json

package.json に Jest の設定を記述する

3つ目の方法は、package.json に Jest の設定を記述する方法です。 package.json に "jest" の項目を作成すると、その内容が Jest の設定になります。

package.json
{
	"name": "demo-app",
	"version": "1.0.0",
	"devDependencies": {
		"jest": "^29.3.1"
	},
	"jest": {
		"collectCoverage": true
	}
}

設定ファイルを自動で作成する

Jest の設定ファイルは、手動で作成することもできますが、自動で作成してくれる機能もあります。

自動で作成するには、jest コマンドに --init オプションを付けて実行します。 この際に、作成されるファイル名は「jest.config.js」になり、フォーマットは JavaScript(CommonJS)になります。

jest --init

設定ファイルの設定項目

どんな設定項目があるかは、ここでは記載しません。 大量にありますので、公式のドキュメントを見てもらった方が良いと思います。

ここでは簡単な例だけ記載しておきます。

jest.config.js
module.exports = {
	collectCoverage: true,
	coverageDirectory: "coverage"
};

コードカバレッジ解析

Jest でテストを実行したときに、テスト対象のコードのうちどれくらいの割合がテストされたかというのが分かります。 追加で何かをインストールする必要はなく、Jest をインストールするだけで使えるようになるのはスゴイです。

coverage.png

コマンドラインにも解析結果の概要が表示されますが、さらにもっと詳細な内容が HTML ファイルとしても出力されます。 どのファイルの、どの行が実行されていないのか、一目でわかるようになっていて便利です。

coverage_html.png coverage_html2.png

コードカバレッジ解析を有効にする

コードカバレッジ解析を有効にする方法は2つあります。

--coverage オプションで指定する

1つ目の方法は、jest コマンドで --coverage オプションを指定する方法です。

jest --coverage

設定ファイルで指定する

2つ目の方法は、設定ファイルで collectCoverage 項目を指定する方法です。 true を設定すると有効になります。

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

コードカバレッジレポートの出力先を変更する

コードカバレッジレポートの出力先のディレクトリは、設定ファイルの coverageDirectory 項目で指定できます。 デフォルトは "coverage" です。 その場合の HTML ファイルの出力先は "./coverage/lcov-report/index.html" になります。

jest.config.js
module.exports = {
	collectCoverage: true,
	coverageDirectory: "coverage"
};

モジュールの読み込みパスを変換する

モジュールを読み込む際に、そのモジュールの場所をパス指定する必要があります。 そのパスに相対パスを使用してしまうと、階層が深くなった場合に "../" だらけになったり、各ファイルで指定するパスの階層が異なったりしてメンテナンスが大変になります。

JavaScript ファイル
// 階層が深いとパスが "../" だらけになる
const sum = require('../../../../../src/sum.js');

Jest では、モジュールを読み込む際のパスを正規表現で変換することができます。

モジュールを読み込む際のパスを正規表現で変換するには、設定ファイルの moduleNameMapper 項目を使用します。 <rootDir> というキーワードを使用すると、その部分はアプリケーションのルートディレクトリに変換されます。

例:パスの指定をアプリケーションのルートディレクトリからの絶対パスに変換する場合
jest.config.js
module.exports = {
	moduleNameMapper: {
		"^/(.*)$": "<rootDir>/$1"
	}
};
JavaScript ファイル
// 変換後はどの階層からでもこう書ける
const sum = require('/src/sum.js');

ES Modules を使用する

Jest の公式のサンプルや、ここまでの説明を見てもわかるように、実は Jest は CommonJS が前提になっています。 JavaScript モジュールを、CommonJS で記述している場合はそれでも問題ないかもしれませんが、 ES Modules で記述している場合は、エラーが発生してしまいます。 これは困るということで、Jest で ES Modules が動作するようにしてみたいと思います。 そもそもなぜ Jest が CommonJS 前提なのかと言えば、Node.js がそうなっているからのようです。

詳しい対応方法は 公式のドキュメント に書かれていますが、 必要なことは大きく2つです。

  1. Node.js に --experimental-vm-modules オプションを付けた状態で Jest を実行すること
  2. Node.js に JavaScript ファイルを ES Modules として認識させること

CommonJS と ES Modules の見分け方

まずは、CommonJS と ES Modules の簡単な見分け方から。 この2つは JavaScript モジュールの記述の仕方が異なります。 module.exports / require を使用するのが CommonJS で、 export / import を使用するのが ES Modules です。

CommonJS
(拡張子は ".js" もしくは ".cjs")
ES Modules
(拡張子は ".js" もしくは ".mjs")
読み込まれる側(sum.js)
function sum(a, b) {
	return a + b;
}
module.exports = sum;
読み込まれる側(sum.js)
export function sum(a, b) {
	return a + b;
}

読み込む側
const sum = require('./sum.js');

console.log(sum(1, 2));
読み込む側
import { sum } from './sum.js';

console.log(sum(1, 2));

Node.js に --experimental-vm-modules オプションを付けた状態で Jest を実行する

Node.js に --experimental-vm-modules オプションを付けた状態で Jest を実行するには、下記のコマンドを実行します。 このオプションは、名前からもわかる通り実験的な機能ということで、今後変更される可能性があります。 しかしながら、いまはこのオプションを使わないといけないようです。

node --experimental-vm-modules node_modules/jest/bin/jest.js

かなり長いので、npm-scripts に登録しておくと楽だと思います(詳しくは後述)。

npm-scripts に登録

package.json
{
	"name": "demo-app",
	"version": "1.0.0",
	"scripts": {
		"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
	},
	"devDependencies": {
		"jest": "^29.3.1"
	}
}

Node.js に JavaScript ファイルを ES Modules として認識させる

Node.js の動作は下記のようになっています。

  • ファイルの拡張子が ".cjs" なら CommonJS として扱う。
  • ファイルの拡張子が ".mjs" なら ES Modules として扱う。
  • ファイルの拡張子が ".js" なら package.json の "type" の値に従う(デフォルトは CommonJS)。

なので、この動作の条件を元に Node.js が ES Modules として認識できるように変更します。

方法は2つです。 環境に合わせてお選びください。

方法1:ファイルの拡張子は ".js" のまま ES Modules として認識させる

Node.js は、ファイルの拡張子が ".js" なら package.json の "type" の値に従います。 "type" の値は、CommonJS の場合は commonjs、ES Modules の場合は module です。 なので、"type" の値に module を指定します。

package.json
{
	"name": "demo-app",
	"version": "1.0.0",
	"type": "module",
	"scripts": {
		"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
	},
	"devDependencies": {
		"jest": "^29.3.1"
	}
}

この値を変更するということは、この package.json が有効な範囲全体に影響するので注意が必要です。 つまり、モジュールとテストコード以外のファイルにも影響が及びます。 拡張子が ".js" のファイルは要注意です。ファイルの中身が CommonJS になっていた場合はエラーになります。

わかりやすい例は設定ファイルです。 例えば、Jest とは別に webpack を使用していて、その設定ファイルが "webpack.config.js" というファイル名だった場合、 そのファイルも ES Modules として扱われてしまいます。 引き続き CommonJS として扱わせたい場合は、そのファイルの拡張子を ".cjs" に変更するなどの対応が必要です。

Jest の設定ファイルが "jest.config.js" というファイル名になっていた場合も同様です。 中身を ES Modules に変更するか、ファイルの拡張子を ".cjs" に変更するか、いっそのこと JSON に変更してしまうかなど、対応が必要になります。

方法2:ファイルの拡張子を全て ".mjs" に変更する

こちらの方法は、全体に影響がない代わりに、モジュールとテストコードのファイルの数に比例して修正箇所が増えます。

Node.js は、ファイルの拡張子が ".mjs" なら ES Modules として扱います。 なので、モジュールとテストコードのファイルの拡張子を全て ".mjs" に変更します。 import 文でファイルパスに拡張子を付けて指定している場合は、そちらも修正が必要です。

次に、Jest が拡張子 ".mjs" をテストコードとして認識できるように、Jest の設定ファイルを変更します。 デフォルトの状態では、".mjs" を認識してくれません。 どのファイルがテストコードとして認識されるかは、testMatch 項目で指定できます。

jest.config.js
module.exports = {
	testMatch: [
		"**/__tests__/**/*.mjs",
		"**/?(*.)+(spec|test).mjs"
	],
};

裏技:webpack でバンドルする

Jest で ES Modules を動かすための裏技として、モジュールとテストコードを webpack でバンドルしてしまうという方法があります。 webpack でバンドルしてしまえば、CommonJS か ES Modules かなんて関係がなくなりますから、Jest で動作させることができます。

しかし、この方法は簡単ではありますが Jest のコードカバレッジ解析できなくなるというデメリットがあります。 また、いちいちバンドルしなければならないという手間も発生します。

コードカバレッジ解析なんていらないぜ!って方は、解決策の1つになるかもしれません。

webpack の使い方がわからないという方は、「webpack の使い方」を参考にしてください。

npm-scripts に登録する

jest コマンドですが、各種オプションなどを毎回入力するのは面倒に感じてくると思います。 そこで、よく使うコマンドを npm-scripts に登録しておくと、入力の手間が少なくなって便利です。

npm-scripts の使い方がわからないという方は、「Node.js と npm の使い方(その3:npm-scripts)」を参考にしてください。

npm-scripts に登録

package.json
{
	"name": "demo-app",
	"version": "1.0.0",
	"scripts": {
		"test": "jest"
	},
	"devDependencies": {
		"jest": "^29.3.1"
	}
}

npm-scripts を実行する

npm run test

オプションを追加したい場合は、-- で繋ぐと追加できます。

npm run test -- --watch

参考サイト

関連記事

JavaScript は文法的にかなりフレキシブルな言語であり、コードが汚くなりがちです。キレイで読みやすいコードを保つために、JavaScript にも静的解析ツールを導入しましょう。ESLint とは?ESLint は、JavaScript の静的解析ツールです。JavaScript コードを実行する前に(つまり静 ...
最近の Web の開発では、webpack を使用することはもはや当たり前のような状況です。そこで改めてその使用方法をまとめてみたいと思います。今更かもしれませんが、誰かの参考になれば幸いです。webpack とは?近年の Web 開発では、サーバーサイド(PHP など)ではなく、クライアントサイド(JavaScrip ...
最近の Web 界隈では、UX を高めるために、UI を JavaScript でガリガリ実装するというのが普通になってます。フロントエンドエンジニアなんていう職種が生まれるほど、この界隈は活発です。そこでフロントエンド開発でほぼ必須となるであろう Node.js と npm について少しまとめてみます。今更感がすごい ...

記事検索

最新記事

人気記事

RSSフィード

お知らせ

  • 2023/01/30 NEW
    ブログ記事「Windows の Docker 環境を高速化する方法」を追加しました。
  • 2023/01/06
    ブログ記事「Jest の使い方」を追加しました。
  • 2023/01/05
    ブログ記事「ESLint の使い方」を追加しました。
  • 2023/01/04
    ブログ記事「webpack の使い方」を追加しました。
  • 2022/12/13
    ツールに「特殊文字変換ツール」を追加しました。
    通常文字と特殊文字の相互変換を行います。対応している文字は英字と数字のみです。
    特殊文字を使用すると、検索エンジンなどで検索しづらくなる可能性があるのでご注意ください。