はじめに
JavaScriptのテストを実行するツールには、npmパッケージとして提供されているものと、Node.jsに組み込まれているものがある。
今回は、その中でも有名なJest、Vitest、さらにNode.js組み込みテストランナーについて調べてみた。
この記事は、こちらの兄弟記事として書いた。
調査
インターネット上では、JestからVitestへ移行した事例が複数見つかる(参考1、参考2、参考3)。
Jestは、Facebook(現Meta)が開発し、2014年にオープンソースとして公開したテストフレームワークである。長く広く利用されてきたため、JavaScriptのテストツールの代表格の一つといえる。
一方、VitestはViteエコシステムを活用した、比較的新しいテストランナーである(v1.0.0は2023年公開)。Vitest公式が特徴の一つとして掲げているのが、Jestとの互換性(Jest compatible)である。JestのAPIや記法を意識して設計されているため、Jestから移行しやすい。Viteの仕組みを利用できることも特徴である。ただし、すべての機能や設定が完全に互換であるという意味ではない。
また、Node.js組み込みテストランナーは、Node.jsに同梱されているテストランナーである。Node.js v18で導入され、v20で安定版になった1。Node.js本体に含まれているため、追加のパッケージをインストールしなくてよいことが特徴である。
それぞれについて、もう少し詳しく見ていく。
Jest
package.jsonの最小構成の例は次のようになる。
{
"scripts": {
"test": "jest"
},
"devDependencies": {
"jest": "^30.5.0"
}
}
テストコードは次のように書ける。
test('成功するテスト', () => {
expect(1 + 1).toBe(2);
});
test('失敗するテスト', () => {
expect(2 * 2).toBe(5);
});
結果は次のとおり。
Jestを使うときは、testやexpectなどが自動的にグローバル変数として注入されるため、import文を明示的に書く必要はない2。
さて、Jestの機能についてだが、CLIを確認すると良いだろう。
一部抜粋して紹介する。
-
--changedFilesWithAncestor、--lastCommit、--onlyChanged(-o)- Gitの変更に関連するテストのみ実行する。
--lastCommitは直前のコミットの変更に関連するテストを実行する。
- Gitの変更に関連するテストのみ実行する。
-
--ci- CI環境を想定した動作にする。新しいスナップショットが必要になった場合は失敗にし、明示的に更新するには
--updateSnapshot(-u)を使う。
- CI環境を想定した動作にする。新しいスナップショットが必要になった場合は失敗にし、明示的に更新するには
-
--clearMocks、--resetMocks- モックをテストごとにリセットする3。
--clearMocksは呼び出し履歴などを消去し、実装は残す。--resetMocksはモックの状態をリセットし、設定した実装も取り除く。
- モックをテストごとにリセットする3。
-
--collectCoverage、--coverage[=<boolean>]- テストカバレッジ情報(コードがどの程度テストで実行されたか)を収集する。カバレッジ関連の設定オプションもある。
-
--findRelatedTests- 指定したソースファイルに関連するテストのみ実行する
-
--maxWorkers=<num>|<string>- ワーカーの最大数を指定する。基本的にはデフォルト(通常時は利用可能なコア数からメインスレッド用に1を引いた数、ウォッチモードでは使用可能なコア数の半分)でよい。
-
--onlyFailures(-f)- 前回失敗したテストのみ実行する。
-
--randomize- テストの実行順をランダム化する。
--seed=<num>でシードを指定すると、同じ順序を再現できる。
- テストの実行順をランダム化する。
-
--watch、--watchAll- ウォッチモード(ファイル変更を監視し、変更があった場合にテストを行う)で実行する。
--watchでは変更ファイルに関連するもの、--watchAllでは全てのテストを実行する。
- ウォッチモード(ファイル変更を監視し、変更があった場合にテストを行う)で実行する。
以前のReact Native公式ドキュメントでは、Jestが推奨テストフレームワークとして紹介されていた(参考)。
現行のReact Testing Libraryでも、Jestを使った例が紹介されている。
ただし、ReactやReact Nativeで使うテストツールはプロジェクトの構成や既存の資産に左右されるため、現在も一律にJestを選ぶべきとは限らない。
Vitest
package.jsonの最小構成の例は次のようになる。
{
"scripts": {
"test": "vitest",
"test:run": "vitest run"
},
"devDependencies": {
"vitest": "^5.0.1"
}
}
vitestを実行すると、開発環境ではウォッチモードで起動する。CIなどで1回だけ実行したい場合は、vitest runを使う。
テストコードは次のように書ける。
import { test, expect } from 'vitest';
test('成功するテスト', () => {
expect(1 + 1).toBe(2);
});
test('失敗するテスト', () => {
expect(2 * 2).toBe(5);
});
結果は次のとおり。
Jest互換を重視して設計されているため、test、expect、モック、スナップショットなど、基本的なAPIはJestと同じ感覚で利用できる。既存のJestテストを移行しやすいことは、Vitestの大きな特徴である。
-
run- ウォッチモードを使わず、1回だけテストを実行する。
-
watch、dev- ウォッチモードで行う。引数なしの場合と同じ
-
related- ソースファイルのリストを対象とするテストのみ実行する
-
--update [type](-u)- スナップショットを更新する設定。
new、all、noneのいずれか
- スナップショットを更新する設定。
-
--coverage.enabled- カバレッジを有効化する。カバレッジ関連の設定オプションは多くある。例えばプロバイダー(
--coverage.provider)はv8、istanbul、customが選べる
- カバレッジを有効化する。カバレッジ関連の設定オプションは多くある。例えばプロバイダー(
-
--browser.enabled- ブラウザ上でテストを実行できる。
--domでDOM環境を再現する場合より低速になりやすいが、実際のブラウザに近い環境でテストできる。
- ブラウザ上でテストを実行できる。
-
--maxWorkers <workers>- 最大ワーカー数を決める。
-
--changed [since]- 変更されたファイルに関するテストのみ実行する。デフォルトは
false。
- 変更されたファイルに関するテストのみ実行する。デフォルトは
-
--sequence.shuffle.tests- ランダムな順番でテストを実行する。
--sequence.seed <seed>でシードを決める。
- ランダムな順番でテストを実行する。
-
--sequence.concurrent- テストを並行実行する。
-
--typecheck.enabled- 型チェックを有効にする。
他にもvitest.config.tsに書いておけるオプションがある。
-
clearMocks、mockReset-
clearMocksはモック関数の呼び出し履歴などを消去し、mockResetは呼び出し履歴に加えてモックの実装もリセットする。Jestにも同様の設定がある。
-
VitestはJestとの互換性を掲げており、基本的なテストコードはJestから移行しやすい。
一方、CLIやモック、設定などには差分がある。例えば、Jestの--onlyFailuresに相当するオプションがVitestにあるわけではないため、目的に応じて--changedなどの別の機能を使うことになる。
Viteのエコシステムを使っていない場合でも選択肢にはなるが、既存のテスト資産や必要な機能、チームの運用を踏まえて選ぶのがよい。
Node.js組み込みテストランナー
package.jsonの最小構成の例は次のようになる。
{
"type": "module",
"scripts": {
"test": "node --test"
}
}
依存関係がいらないのは特徴である。
テストコードは次のように書ける。
import test from 'node:test';
import assert from 'node:assert';
test('成功するテスト', (t) => {
assert.strictEqual(1 + 1, 2);
});
test('失敗するテスト', (t) => {
assert.strictEqual(2 * 2, 5);
});
結果は次のとおり。
ドキュメントを見て、主な機能を確認する。
-
--watch- ウォッチモード。テストファイルや依存ファイルの変更を監視し、影響を受けるテストを再実行する。実験的機能。
-
--test-randomize、--test-random-seed=<seed>- 実行順序のランダム化。比較的新しい機能で、利用するNode.jsのバージョンによっては使えない。
-
--experimental-test-coverage- カバレッジの収集。V8を使用する。実験的機能。
-
--test-update-snapshots- スナップショットの生成。コード内で
t.assert.snapshotを使用すると、値の一致を確認できる。
- スナップショットの生成。コード内で
また、テストコードファイルからAPIを呼び出して実行することもできる。
例えば、node:testからrun関数を、node:pathからpathをインポートし、run({ files: [path.resolve('./tests/test.js')] })...のように呼び出して実行できる。
詳細は公式ドキュメントを参照。
-
run([options])-
concurrency:Number | Boolean- テストファイルの並列数を決める。
trueの場合はos.availableParallelism() - 1、falseの場合は1。デフォルトはfalse。
- テストファイルの並列数を決める。
-
watch、randomize、coverage- 上記参照
-
JestやVitestと比べると、テストの探索・実行や標準的なアサーションに機能を絞った構成である。
豊富なCLIや周辺機能が必要な場合は物足りない可能性があるが、依存パッケージを増やしたくない場合や、Node.jsの機能だけでテストを構成したい場合には有力な選択肢になる。
もう少し機能が増えてきたらそれ以外の場合でも使えるようになるのでは、といった感じ。
終わりに
Jest、Vitest、Node.js組み込みテストランナーを比較すると、機能の豊富さや既存資産を重視するならJest、Viteとの統合やJestに近い記法を重視するならVitest、依存パッケージを増やしたくないならNode.js組み込みテストランナー、というように選ぶのがよさそうだ。
個人的な意見を言うと、一から新しいプロジェクトを始めるのであればVitestが優勢かな、とは感じている。
既存の資産があるならJestも有力な選択肢になってくるが、今後のことを考えるとVitestに変わっていくのかな、と思う。
実際、npmの週間ダウンロード数は、確認時点(2026年9月20日週)でJestが約5400万、Vitestが約1.2億である(Jest、Vitest)。
Vitestは今年に入ってから大きく伸びているように見える(2025年12月時点で~2000万、これはAIエージェントの影響もあるように思うが)。
Node.js組み込みテストランナーに関しては、今後どのように機能を広げていくかにも注目したい。
しかし、実際に選ぶときは、必要な機能、既存コードとの互換性、チームでの保守しやすさを基準にするべきではあるので、そこは気を付けたい。
-
Nodeのバージョンコードネームが元素由来なのは初めて知った。現行のv26はLithiumあたりになるのだろうか。Qとかどうするんだろう?400番台(Quadiで始まるから)か?あとなんでv14はFluorineでなくFermiumなんだ...? ↩
-
明示的に書くことが好まれる場面もある。
importする場合は@jest/globalsからインポートする。--injectGlobalsをfalseにすると明示的なインポートが必須になる。 ↩ -
状態のリセットが必要な時とはどのようなときかというのは個人的に疑問だったが、たとえば
mockReturnValueOnce()を使うときなどは状態が必要になってくる。 ↩


