# Vitest vs node:test: どちらのJSテストランナー?

> Vitest vs Node組み込みのテストランナー: Vite駆動の高速なフレームワーク vs 依存関係ゼロのnode:test。機能、速度、CIへの適合性を比較。

Source: https://latchkey.dev/ja/learn/tool-comparisons/vitest-vs-node-test  
Updated: 2026-08-20

Vitestは、豊富な機能を備えたVite駆動の高速なテストフレームワークです。node:testは、Nodeに同梱される組み込みで依存関係ゼロのランナーです。

VitestはVite設定とtransformを再利用し、watch modeでテストを並列実行し、JestライクなAPI、モック、coverage、スナップショットテストを提供します - Vite/TSプロジェクトに理想的です。node:testはモダンなNodeに組み込まれ、依存関係を必要とせず、中心的なニーズ(test、サブテスト、node:assertによるアサーション、基本的なモック)をカバーしますが、よりミニマルです。Vitestは機能豊富で、node:testは軽量で依存関係がありません。

## Comparison

|  | Vitest | node:test |
| --- | --- | --- |
| 依存関係 | Vitest + Vite | なし(組み込み) |
| 機能 | モック、スナップショット、coverage | 中心的なテスト機能 |
| TS / Vite | ファーストクラス | 手動transform |
| watch mode | 高速なHMR | 基本的 |
| 最適な用途 | Vite/TSアプリ | 軽量で依存関係ゼロのテスト |
| watchモード | あり (実験的フラグ) | あり。HMR風の再実行 |
| TypeScript | ネイティブの型除去 | Vite経由でそのまま |
| JSXとコンポーネントテスト | なし | Vue、React、Svelteなど |
| ブラウザモード | なし | あり |
| CIレポーター | spec、tap、dot、junit、lcovを内蔵 | 複数。加えて分散CI向けのシャーディング |
| ベンチマーク | なし | あり。Tinybench経由 |
| 型テスト | なし | あり。expect-type経由 |
| テストタグ | あり。Node 26.2.0以降 | フィルタリング経由 |

## CIでの利用

Vitestは、すでにViteを使っているプロジェクトや、coverageとスナップショットを備えたフル機能のランナーが欲しいプロジェクトにフィットし、CIでうまく並列化します。node:testは、テストの依存関係を持ちたくなく高速な起動が欲しいライブラリに魅力的ですが、TSのtransformとcoverage(c8経由)は自分で扱います。アプリの機能の深さにはVitestを、最小限のフットプリントにはnode:testを選びましょう。

- `--test-reporter=junit` and `=lcov` are built in, so CI test reporting and coverage upload need no extra packages.
- `--test-concurrency=N` controls parallel test files; concurrency is not an afterthought.
- `--test-randomize` with `--test-random-seed` makes order-dependence reproducible instead of mysterious.
- `--test-rerun-failures <state-file>` reruns only what failed, which is a meaningful CI optimisation.
- `--experimental-test-coverage` produces V8 coverage without instrumenting your build.

```node:test, current capabilities
import { test, describe, it, mock, snapshot } from 'node:test';
import assert from 'node:assert';

// module mocking, not just function mocking
mock.module('./payments.js', {
  namedExports: { charge: mock.fn(() => ({ ok: true })) },
});

// timer mocking
mock.timers.enable({ apis: ['setTimeout', 'Date'] });

describe('checkout', () => {
  it('charges once', async (t) => {
    const spy = t.mock.method(cart, 'total');
    await checkout(cart);
    assert.strictEqual(spy.mock.callCount(), 1);
  });
});
```

> Coverage is the one headline feature still behind an experimental flag. It works and it emits lcov, but if your organisation forbids experimental flags in CI, that single constraint may decide this comparison for you.

## 高速化する

依存関係をキャッシュし、テストをジョブ間でシャーディングして実時間を短縮します。どちらもCIランナー上で実行され、より高速なマネージドランナーはテスト実行とtransformのステップを短縮します。

- **Component testing.** Vue, React, Svelte and other component tests need a transform pipeline and a DOM. Vitest inherits both from Vite; node:test has neither.
- **Browser mode.** Running the suite in a real browser rather than a simulated DOM has no built-in equivalent.
- **JSX and framework transforms.** Vitest reuses your existing Vite config, resolvers, and plugins, so tests see the same module graph as your app. Reproducing that under node:test means building it yourself.
- **Benchmarking and type testing.** Tinybench-backed benchmarks and expect-type assertions have no built-in counterpart.
- **Sharding.** Vitest supports splitting a suite across CI machines natively, which matters once a suite is long enough to need it.
- **Jest compatibility.** The Jest-compatible `expect` and mocking API makes migrating an existing Jest suite mostly mechanical.

> Notice what is not on this list: mocking, snapshots, watch, concurrency, and CI reporters. Those used to be the argument for reaching for a framework and they no longer are.

## 判断ルール: 何をテストしているのか?

正直な分かれ目は、機能か依存関係かではありません。テストがブラウザ形の環境とビルドのtransformを必要とするかどうかです。

| テスト対象 | 選ぶべきもの | 理由 |
| --- | --- | --- |
| 公開するnpmライブラリ | node:test | テスト経路に依存関係がゼロで、リリース周期に追随すべきものもない |
| バックエンドサービスやCLI | node:test | DOMもtransformも不要。組み込みの機能で足りる |
| React、Vue、Svelteのアプリ | Vitest | コンポーネントテストにはViteのtransformパイプラインとDOMが要る |
| すでにViteを使っているもの | Vitest | テスト設定が既存のアプリ設定そのもの。2つ目のモジュールグラフを保守しなくてよい |
| 移行したいJestのスイート | Vitest | Jest互換のexpectとmockにより、ほぼ機械的に済む |
| ベンチマークや型テストが要るスイート | Vitest | 組み込みの同等物が存在しない |
| 両方があるmonorepo | 両方 | runnerの選択はパッケージ単位。混在を禁じる決まりはない |

## 起動コストとCI時間

CIで組み込みrunnerを選ぶ実際的な論拠は、テストを走らせる前にインストールするものが何もないことです。コールドなCI jobでは、テストフレームワークとそのtransformチェーンの `npm ci` は毎回の実時間であり、アサーションが1つも実行される前に費やされます。

- node:testはインストールも解決も追加しません。`node --test` はrunnerにすでにあるNodeで動きます。
- VitestはVitestとViteとその推移的依存を取得し、初回実行でtransformパイプラインを温めます。
- 一方でVitestのシャーディングは、大きなスイートの総実時間を、インストール時間の損失をはるかに上回って削減できます。

> この損得はスイートの規模で反転します。小さなライブラリではインストールが支配し、総CI時間ではnode:testが勝ちます。大きなアプリのスイートでは実行が支配し、Vitestのシャーディングが勝ちます。どちらかを仮定する前に、自分のインストールと実行の内訳を測ってください。

## それぞれをCIで走らせる

```GitHub Actions steps
# node:test - nothing to install
- run: node --test --experimental-test-coverage \
    --test-reporter=junit --test-reporter-destination=junit.xml \
    --test-reporter=lcov --test-reporter-destination=lcov.info

# Vitest
- run: npm ci
- run: npx vitest run --coverage --reporter=junit --outputFile=junit.xml

# Vitest, sharded across 4 machines
- run: npx vitest run --shard=${{ matrix.shard }}/4
```

> どちらもJUnit XMLとlcovを出力するため、テストレポート、カバレッジのアップロード、PRへの注釈はどちらでも同じように動きます。これはかつて組み込みrunnerを避ける理由でしたが、今はそうではありません。

## 相互に移行できるか?

部分的に、そして方向が重要です。Vitestからnode:testへは、素の単体テストなら機械的です。`describe`/`it` の形は同じで、主な作業は `expect(...)` のアサーションを `node:assert` に書き換え、モック呼び出しを対応付け直すことです。コンポーネントテストの移行は書き換えではなく作り直しになります。transformパイプラインとDOMに組み込みの同等物がないからです。

- アサーション: `expect(a).toBe(b)` は `assert.strictEqual(a, b)` になります。ほぼ検索置換ですが、深い等価性には注意が要ります。
- モック: `vi.fn()` は `mock.fn()`、`vi.mock()` は `mock.module()` になりますが、APIは同一ではありません。
- スナップショット: どちらにもありますが、ファイル形式が異なるため、移植ではなく再生成を見込んでください。
- コンポーネントテスト: 経路はありません。これらはVitestに残ります。

## The switching cost is mostly in the parts nobody lists

- Assertions and mocks usually port mechanically when the target implements a compatible API; custom transformers and framework plugins do not.
- Snapshot formats differ between runners, so plan to regenerate and review rather than port.
- Run both suites in parallel in CI for a period and diff the results. A migration that changes which tests fail is not a migration, it is a regression you have not found yet.
- Coverage numbers move on a runner change even when the tests do not, because instrumentation differs. Re-baseline any coverage gate deliberately.

## 結論

ViteまたはTypeScriptアプリを構築し、モック、スナップショット、coverageを標準で欲しい場合: Vitest。依存関係ゼロと高速な起動を重視するライブラリを書く場合: node:test。ほとんどのアプリチームはVitestを選び、ミニマリストやライブラリ作者はますますnode:testを使います。

## FAQ

### Node組み込みのテストrunnerは本番で使えますか?

はい。`node:test` はNode 20以降Stableとされており、実験的ではありません。スナップショットテストはv22.3.0で安定し、グローバルのsetupとteardownはv24で入りました。まだ実験的フラグの背後にあるのはコードカバレッジで、`--experimental-test-coverage` を使います。

### node:testはモックに対応していますか?

はい、しかも多くの比較が示すよりも本格的に対応しています。関数、メソッド、プロパティ、getterとsetter、そして `mock.module()` によるモジュール全体をモックでき、さらに `mock.timers` で `setTimeout`、`setInterval`、`Date` を含むタイマーも扱えます。

### node:testはTypeScriptを実行できますか?

はい。Nodeはネイティブに型を除去し、`--no-strip-types` を渡すと既定のテストファイルパターンが `.ts`、`.mts`、`.cts` を含むよう拡張されます。素直なTypeScriptなら別途transformは不要ですが、Viteのpluginやパスエイリアスに依存するものは依然として設定が要ります。

### Vitestはnode:testより速いですか?

どこを測るかによります。node:testはフレームワークのインストールと解決を回避するため、小さなスイートでは総CI実時間でたいてい勝ちます。Vitestはマシン間のシャーディングに対応し、スイートが大きくなれば決定的に勝ちます。仮定せず、インストールと実行の内訳を測ってください。

### node:testをReactのコンポーネントテストに使えますか?

実用的には無理です。コンポーネントテストにはDOMとJSXのtransformパイプラインが要りますが、node:testはどちらも提供しません。VitestはViteから両方を継承します。コンポーネントをテストするなら、それだけでこの比較の結論が決まります。

### node:testはCIのテストレポートやカバレッジツールと連携できますか?

はい。`junit` と `lcov` のレポーターを同梱しているため、テストレポート用のJUnit XMLとカバレッジアップロード用のlcovが追加パッケージなしで動きます。これはかつてCI上で最も強かった反対論拠を取り除きます。

### Vitestからnode:testへどう移行しますか?

素の単体テストならほぼ機械的です。`describe`/`it` の構造はそのまま引き継がれ、`expect(...)` のアサーションは `node:assert` の呼び出しに、`vi.fn()`/`vi.mock()` は `mock.fn()`/`mock.module()` になります。スナップショットの形式は異なるので再生成を見込んでください。コンポーネントテストは移行できず、Vitestに残すべきです。

### ライブラリ作者はVitestとnode:testのどちらを使うべきですか?

多くの場合node:testです。公開ライブラリは、テスト経路に最新へ追随すべきものがないこと、利用者側の構成に対してデバッグすべきtransformチェーンがないこと、追いかけるべきフレームワークのリリース周期がないことから利益を得ます。組み込みrunnerは、ライブラリのテストスイートが通常必要とするものを今やカバーしています。

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
