おそらく、あなたは次の2つの状況のどちらかにいるはずです。JavaScript プロジェクトにテストがほとんどなく、リファクタリングが毎回リスクに感じられるか、テストは存在するものの、その半数が遅く、脆く、信頼しにくい状態になっているかのどちらかです。
さらに悪化するのが Capacitor と Electron アプリです。シンプルな機能でも、共有ビジネスロジック、ブラウザAPI、ネイティブプラグイン、ローカルファイル、IPC、リモートサービスが同じフローで絡み合うことがあります。これらのテスト方法を誤ると、テストスイートは偽の依存関係の迷宮になります。正しい方法でテストすれば、壊れやすいロジックに対して素早いフィードバックを得られます。
優れたJavaScriptのユニットテストは、巧妙なマッチャー構文から始まるものではありません。始まりは規律ある境界です。純粋なロジックは直接テストし、副作用は分離し、内部関数の名前を変えた途端に壊れるテストは書かないことです。
目次
- JavaScriptテストフレームワークの選び方
- プロジェクトのセットアップと最初のテスト
- Mockと非同期 Code をマスターする
- 堅牢なテストのための高度な戦略
- CI、Capacitor、および Electron アプリのテスト
- JavaScript のユニットテストに関するよくある質問
JavaScriptテストフレームワークの選び方
プロフェッショナルなJavaScriptプロジェクトには、本格的なテストランナーが不可欠です。複数のエンジニアが同じコードベースを触るようになると、アドホックなスクリプトや手動のコンソール確認ではスケールしません。テストの自動検出、アサーション、非同期処理、モック、そしてローカル開発とCIの両方で一貫して実行できる仕組みが必要です。
現在の指針は、少数のメインストリームな選択肢に収束しつつあります。 Jest、Mocha、Jasmine これらは主要なフレームワークとして繰り返し挙げられており、 Jest 組み込みのテスト構造、アサーション、モック、非同期サポートを1つのパッケージに備えている点で特に注目されることが多く、こちらの Pluralsight JavaScript テストラボ.

なぜフレームワークが必須なのか
チームが最初にやりがちなミスは、ユニットテストを片手間の作業と捉えることです。そうなると、ファイル命名がバラバラになり、誰も覚えていない独自のアサーションや、特定の人しか理解できないヘルパーが生まれがちです。
フレームワークは共通言語を与えてくれます:
- テスト構造 を使用して
describeとtestまたはit - アサーション 読みやすいマッチャー
- フック セットアップとティアダウン用
- 非同期サポート Promiseとタイマー用
- モックツール 外部依存関係用
ユニットレベルの作業を超えたテスト自動化のより広い視点が必要な場合、Capgo には アプリ配信ワークフローにおける自動テストの有用な概要があります.
Jest vs Mocha の比較
Jest と Mocha は2つの異なる哲学を表しています。
Jest オールインワン型の選択肢です。チームが初日から必要とする機能のほとんどが標準搭載されています。
Mocha よりモジュール化されています。ランナーを提供し、スタックの残りは自分で組み立てることを前提としています。
| 機能 | Jest | Mocha |
|---|---|---|
| セットアップの複雑さ | 多くのチームで低め | 通常はアサーションやモックライブラリを追加するため高め |
| アサーション | 組み込み | 通常は別のライブラリと併用 |
| モック | 組み込み | 通常は別のライブラリと併用 |
| 非同期テスト | 組み込み済みでシンプル | サポートされていますが、周辺のセットアップに依存します |
| カバレッジワークフロー | 同じツールチェーンに統合されることが多い | 個別に組み合わせて使うことが多い |
| 最適 | 新規プロジェクト、一貫性を重視するチーム | レガシースタック、モジュール単位の制御を重視するチーム |
実践的なルール: テストランナーにどのアサーションライブラリとモックライブラリを組み合わせるか迷うなら、Jestがおすすめです。
多くのチームにおすすめ
ほとんどのモダンなプロジェクトなら、これを選びます Jest ただし、コードベースにすでに Mocha を使い続ける強い理由がある場合を除きます。アプリケーションに Capacitor または Electronが含まれる場合、この推奨はさらに強くなります。なぜなら、そうしたプロジェクトはすでに考慮すべき要素が十分多いからです。テストツールの乱立を減らすことは、すぐに効果を発揮します。
エコシステムがすでに固まっている古い Node.js サービスや長く運用されているコードベースでは、Mocha は依然として理にかなっています。しかし、堅牢なスイートをゼロから構築する中堅エンジニアにとっては、通常 Jest の方が摩擦を生み出す以上に取り除いてくれます。
スコープに関する重要な注意点です。Cypress や Playwright は優れたツールですが、解決する問題が異なります。これらはブラウザレベルや E2E のチェックには適していますが、JavaScript のユニットテストが本来あるべき高速なインナーループには向いていません。
プロジェクトのセットアップと最初のテスト
クリーンなテスト環境は、退屈なものであるべきです。最初のテストを追加するのが複雑に感じるなら、そのテストスイートは健全に保たれないでしょう。

シンプルなJestのセットアップ
まずは、すでに次を備えた JavaScript プロジェクトから始めましょう。 package.json。次に Jest を開発依存関係として追加し、テストスクリプトを設定します。
{
"scripts": {
"test": "jest"
}
}
多くのプロジェクトではこれで十分です。モジュールシステム、トランスパイル、モノレポ構成に応じて、後から設定を追加できます。
ローカルで Capacitor アプリを構築していて、共有ロジックのテストを追加する前に開発環境を整えたい場合は、Capgo の Capacitor のローカル環境セットアップ が実用的な参考になります。
code を書く前にテストを書く
テストファーストのパターンは単なる個人の好みではありません。米国消費者金融保護局の JavaScript ガイドラインでも明確に推奨しています。 テストを先に書く、テストを整理する describe と it、そしてチェックを組み立てる expect(...) その中のassertions JavaScriptユニットテストのガイドライン.
だから重要です。テストファーストはcodeの設計方法を変えるからです。関数は小さくなり、依存関係が見えやすくなり、純粋に保つべきロジックに副作用が漏れ出なくなります。
最小限の例はこちらです:
// math.js
function addTax(amount, rate) {
return amount + amount * rate;
}
module.exports = { addTax };
// math.test.js
const { addTax } = require('./math');
describe('addTax', () => {
it('returns the amount with the tax applied', () => {
expect(addTax(100, 0.2)).toBe(120);
});
});
常に Arrange-Act-Assert パターンを使う
この Arrange, Act, Assert パターンは、テストが複雑になっても読みやすさを保ちます。
- Arrange 入力と必要なセットアップを準備します。
- Act 関数を呼び出します。
- Assert 結果を検証します。
バリデーションヘルパーに適用すると:
function isSupportedPlatform(platform) {
return ['ios', 'android', 'web', 'desktop'].includes(platform);
}
describe('isSupportedPlatform', () => {
it('returns true for ios', () => {
// Arrange
const platform = 'ios';
// Act
const result = isSupportedPlatform(platform);
// Assert
expect(result).toBe(true);
});
});
小さなテストは長く使えます。テストは通常、1つの質問に答えるだけでよく、ワークフロー全体を語る必要はありません。
CapacitorやElectronプロジェクトでは、純粋なロジックがネイティブやデスクトップ統合のcodeの隣に置かれることが多いため、この規律がより重要になります。プラットフォームランタイムなしでビジネスルールをテスト可能に保てば、最初のテストが最後の有用なテストになることはありません。
Mockと非同期 Code をマスターする
アプリケーションのcodeにおける多くのバグは、2つの数を足すことではなく、自分自身を超えて到達するcodeから生じます: ネットワークリクエスト、ファイル、プラグインAPI、タイマー、IPCチャネル、ストレージレイヤーなどです。
ここでモックが役立ちます。境界を制御できるため、テストはcodeの意思決定に集中できます。

すべてではなく、境界をモックする
保守可能なテストガイダンスは、 単一動作のカバレッジ と 1テストにつき強いアサーションを1つ、また、モックの使いすぎはテストを壊れやすくし、実装の詳細と密結合させてしまうという警告も、こちらの 保守性の高いユニットテストに関するTestRailの記事.
にまとめられています。JavaScriptでは、この警告は非常に重要です。多くのチームは、インポートしたすべてのモジュールをモックすることから始め、実際の振る舞いをテストするのではなく、関数が「正しい」順序で他の関数を呼び出しているかどうかをテストすることに終始してしまいます。
モックを多用するテストに不向きな対象:
- ヘルパーAがヘルパーBを呼び出したか
- サービスCがシリアライザDを呼び出したか
- 内部のプライベート関数が2回実行されたか
より適切なテスト対象:
- 関数が何を返したか
- 依存関係の失敗を正しく処理できたか
- データを期待通りの形式に変換できたか
Capacitor と Electron code のためのより良いパターン
モバイルやデスクトップアプリでは、ネイティブやプラットフォームAPIの周りにラッパー層を置くのが好みです。そうすればユニットテストはプラットフォーム自体ではなく、ラッパーをモックできます。
構成例:
// cameraGateway.js
async function getPhoto(cameraPlugin) {
return cameraPlugin.getPhoto();
}
module.exports = { getPhoto };
// profilePhotoService.js
async function loadProfilePhoto(cameraGateway) {
const photo = await cameraGateway.getPhoto();
return { path: photo.path, ready: true };
}
module.exports = { loadProfilePhoto };
// profilePhotoService.test.js
const { loadProfilePhoto } = require('./profilePhotoService');
test('returns mapped photo data', async () => {
const fakeCameraGateway = {
getPhoto: jest.fn().mockResolvedValue({ path: '/tmp/pic.jpg' })
};
const result = await loadProfilePhoto(fakeCameraGateway);
expect(result).toEqual({ path: '/tmp/pic.jpg', ready: true });
});
このパターンはElectronでも有効です。薄いアダプターの背後にラップするのは ipcRendererファイルアクセスやシェル連携です。ユニットテストはランタイムを直接叩かず、サービス層を叩きます。
Capacitor アプリでリリースロジックやアップデートパスをテストしているチーム向けに、Capgo には関連ガイドがあります: モックシナリオを使った Capacitor OTAアップデートのテスト.
チーム内で非同期テストのスタイルがまだ定着していないなら、クイックウォークスルーが役立ちます:
不安定さを排除した非同期フローのテスト
使用する async/await テスト内で、テスト対象の code がPromiseを返す場合は、コールバック多用のパターンより明確でデバッグも容易です。
async function fetchProfile(api) {
const response = await api.getUser();
return response.name;
}
test('returns the user name from the API response', async () => {
const api = {
getUser: jest.fn().mockResolvedValue({ name: 'Ava' })
};
const result = await fetchProfile(api);
expect(result).toBe('Ava');
});
失敗時のパスもテストしましょう:
test('throws when the API request fails', async () => {
const api = {
getUser: jest.fn().mockRejectedValue(new Error('network failed'))
};
await expect(fetchProfile(api)).rejects.toThrow('network failed');
});
正常系と異常系の両方をテストしましょう。本番では、ユーザーの記憶に残るのは大抵、異常系のほうです。
堅牢なテストのための高度な戦略
テストスイートは、code が変わった後も役立ち続けてこそ有用です。それは、通るテストを山ほど書くより難しいことです。

テストの配分を予算として考える
ある実践的なガイドでは、次の 70/20/10 配分を推奨しています: ユニットテスト、統合テスト、E2Eテスト。ユニットテストはフィードバックが最も速く、失敗の再現性も高いのが特徴です。同ガイドでは、ユニットテストスイート全体は理想的には 10秒以内に終了すべきであり、pre-commit チェックは 5秒以内に収めるべきですだったと、この OpenReplay テストガイド.
私はテストを宗教ではなく、予算管理ツールとして捉えています。労力の大半をE2Eテストに注ぐと、チームはフィードバックを得るまで待たされすぎます。すべてをユニットテストだけにすると、実際のシステム境界を見逃します。
Capacitor や Electron アプリの場合、健全なバランスは通常次のようになります:
- ユニットテスト 価格ロジック、権限ルール、シリアライズ、アップデート適用条件、フィーチャーフラグ、状態変換向け
- 統合テスト ストレージアダプター、プラグインラッパー、IPCコントラクト向け
- E2Eテスト ログイン、購入フロー、同期、アップデートプロンプトなど、いくつかの重要なジャーニー向け
カバレッジは目標ではなく、懐中電灯だ
カバレッジレポートは、重要なロジックの未テスト分岐を見つけるのに役立つなら有用です。チームがカバレッジ率を追うこと自体を目的にすると、有害になります。
エッジケースをしっかり考えたログインバリデーターのテストは、些細なアサーションで埋め尽くされたカバレッジだけを満たすファイルよりも価値があります。フォーム、パーサー、日付ロジック、権限チェックなど、入力の多いcodeではなおさらです。バリデーション中心のUIの品質を高めているチームには、このガイドは フロントエンドのフォームバリデーションをマスターする のに役立ち、ユニットレベルのテスト戦略の良い補完になります。
振る舞い重視のテストはリファクタリングに強い
信頼できるテストスイートなら、テストの半分を書き直さずに内部実装をリファクタリングできるはずです。そこに至る最も簡単な方法は、 観測可能な振る舞い を、実装の詳細ではなくアサートすることです。
堅牢に保たれるユースケース:
- 境界条件 空の入力、null相当の値、無効な型、過大な文字列など
- ドメインの結果 「権限が不足している場合、拒否を返す」など
- 状態遷移 「ダウンロードメタデータが検証された後、更新を保留としてマークする」など
脆くなりやすいユースケース:
- 内部ヘルパーの呼び出しを検査する
- プライベートメソッドの順序をアサートする
- 呼び出しチェーンのすべてのレイヤーをモックする
規律あるリリースプロセスを構築するアプリチームにとって、Capgoの アプリの品質保証 テスト作業をより広いリリースパイプラインに接続するため、有用です。
CI、Capacitor、および Electron アプリのテスト
1人の開発者のマシンでのみ実行されるテストは、セーフティネットではありません。それはローカルでの習慣にすぎません。
CIは、JavaScriptのユニットテスト作業をチームのインフラストラクチャに変えます。すべてのpush、pull request、リリースブランチで、同じコマンドを同じ期待値で実行できます。CapacitorやElectronプロジェクトでは、環境のズレが微妙な失敗を引き起こすため、この一貫性はさらに重要になります。
CI をデフォルトの実行パスにする
最低限、CIは変更のたびに依存関係をインストールし、ユニットテストを実行する必要があります。可能な限り、ローカル開発時と同じコマンドを使用してください。
基本的な GitHub Actionsのワークフローは、これほどシンプルです:
name: test
on: [push, pull_request]
jobs:
unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test
これだけで、インポートエラーやアサーションの失敗、意図しないプラットフォーム依存による問題を、mainブランチにマージされる前に検知できます。
自動化パイプラインでリリースしているモバイルチーム向けに、Capgo には実用的なガイドがあります: Capacitor アプリのCI/CD設定方法.
Capacitor プラグインの相互作用をテストする
Capacitor code のユニットテストにおける誤った手法は、すべてのサービスにネイティブプラグインを直接取り込むことです。これではテストスイートがプラットフォームブリッジに密結合してしまいます。
より良いパターンは、薄い抽象化レイヤーを設けることです:
// deviceStorage.js
async function saveFile(filesystem, path, data) {
return filesystem.writeFile({ path, data });
}
module.exports = { saveFile };
// draftService.js
async function persistDraft(storage, draft) {
await storage.save('draft.json', JSON.stringify(draft));
return { saved: true };
}
module.exports = { persistDraft };
// draftService.test.js
const { persistDraft } = require('./draftService');
test('persists a serialized draft', async () => {
const storage = {
save: jest.fn().mockResolvedValue(undefined)
};
const result = await persistDraft(storage, { title: 'Hello' });
expect(result).toEqual({ saved: true });
});
カメラアクセス、生体認証プロンプト、push tokenの登録、ネットワーク状態についても同様です。プラグインの呼び出しはアダプター内に閉じ込め、自身で制御可能なインターフェースに対してアプリのロジックをテストしてください。
Electron のメイン、レンダラー、IPC code のテスト
Electronアプリには、重要なseam(継ぎ目)が2つあります: メインプロセス code と レンダラープロセス code. テストでこれらを曖昧にしないでください。
信頼性の高い構成では、通常以下のように分離します:
- レンダラーのユニットテスト ビューモデル、状態管理、フォーマット、UI側のビジネスロジック用
- メインプロセスのユニットテスト メニュー、ファイル操作、アプリのライフサイクル判断用
- IPCコントラクトテスト メッセージの形式と期待されるレスポンスの検証用
IPCラッパーの例:
// ipcGateway.js
function sendSettings(ipcRenderer, payload) {
ipcRenderer.send('settings:update', payload);
}
module.exports = { sendSettings };
// ipcGateway.test.js
const { sendSettings } = require('./ipcGateway');
test('sends settings update over ipc', () => {
const ipcRenderer = { send: jest.fn() };
sendSettings(ipcRenderer, { theme: 'dark' });
expect(ipcRenderer.send).toHaveBeenCalledWith('settings:update', { theme: 'dark' });
});
後で内部実装を別のヘルパーに変えても、このテストは重要な動作を検証しているので引き続き有効です。デスクトップとモバイルのcode全体で目指したい基準です。
JavaScript のユニットテストに関するよくある質問
ユニットテスト、統合テスト、E2E テストの違いとは
ある ユニットテスト は、小さなロジックを1つだけ切り離して検証します。一方、 統合テスト いくつかのコンポーネントやサービスが正しく連携して動作するかを確認します。 エンドツーエンドテスト 実行中のアプリケーションでユーザージャーニーを実際にたどって検証します。
ビジネスルールの迅速な検証にはユニットテストを、ストレージやプラグインラッパー、IPC などの境界には統合テストを、壊れると深刻な影響が出るワークフローには E2E テストを控えめに使いましょう。
カバレッジ100%を目指すべきか
いいえ。完全なカバレッジは、チームを価値の低いテストに走らせる可能性があります。
カバレッジは、誰も実行していないリスクのあるcodeを明らかにする場合に役立ちます。エンジニアがダッシュボードを満たすためだけに浅いアサーションを追加するなら、役立ちません。テストスイートがもろい場合、カバレッジを増やしても救えません。
既存のコードベースにテストを追加する方法
変更がすでに起きているところから始めましょう。チームを止めて、テスト戦略の大規模な書き換えを宣言するのはやめましょう。
実用的な進め方は次のとおりです:
- まずはアクティブなcodeを守る 機能開発やバグ修正で触るモジュールにテストを追加する
- 純粋なロジックを抽出 テストしにくいファイルから純粋なロジックを抽出し、フレームワークやランタイムのノイズなしにビジネスルールをテストできるようにします
- シームラッパーを追加 ネイティブプラグイン、ネットワーククライアント、ファイルシステム呼び出し、Electron IPC の周りにシームラッパーを追加します
- 脆いパターンを拒否 モックを導入する際に脆いパターンを拒否します。 JavaScript テストのベストプラクティス は、過剰なモック化という見落とされがちな問題と、それに伴う脆いテストを強調しているため、ここでは特に役立ちます
目標は即座の完全性ではありません。リグレッションがチームに最もコストをかける箇所で、着実に改善することです。
チームがリリースしている場合 Capacitor または Electron アプリで JavaScript の変更をめぐるリリースプロセスをよりクリーンにしたいなら、 Capgo 検討すべき選択肢のひとつです。CapacitorJS と Electron アプリにライブアップデートを提供し、ロールアウト制御とオブザーバビリティを備えているため、チームは堅牢なユニットテストと組み合わせて、修正のたびにストアのレビューを待たずに Web バンドルの変更をより安全にリリースできます。