現在、2 つの状況のいずれかにいるかもしれません。JavaScript プロジェクトがほとんどのテストがなく、リファクタリングがリスクが高く感じている場合、または既存のテストが半分以上が遅い、脆弱で、信頼できないと感じている場合。
それが悪化するのは Capacitor と Electron アプリケーションです。単純な機能が共有ビジネスロジック、ブラウザAPI、ネイティブプラグイン、ローカルファイル、IPC、リモートサービスに触れることができるため、同じフローでテストする必要があります。テストを間違った方法で行うと、スイートは偽の依存関係の迷路になります。正しい方法でテストすると、論理が破壊されるたびに高速なフィードバックが得られます。
良いユニットテストのJavaScriptワークは、巧妙なマッチャーシntaxで始まるのではなく、厳格な境界から始まります:純粋な論理を直接テストし、副作用を分離し、内部関数をリネームするとすぐにテストが崩れるものを書かないようにします。
目次
- JavaScript テストフレームワークを選択する
- プロジェクトのセットアップと最初のテスト
- モックと非同期Codeのマスター
- 堅牢なテストのための高度な戦略
- CI、Capacitor、およびElectronアプリのテスト
- JavaScriptユニットテストに関するよくある質問
JavaScriptテストフレームワークの選択
プロフェッショナルなJavaScriptプロジェクトには、実際のテストランナーが必要です。アドホックのスクリプトと手動のコンソールチェックは、複数のエンジニアが同じコードベースに触れるとスケールしなくなります。テストの自動検出、断言、非同期処理、モック、ローカル開発とCIで一貫して実行できる方法が必要です。
現在のガイドラインは、主流のオプションの小さなセットに収束しています。 Jest、Mocha、Jasmine は、主なフレームワークとして繰り返し強調されています。 Jest は、組み込みのテスト構造、断言、モッキング、非同期サポートが一つのパッケージに含まれているため、よく単独で強調されています。 この.

で示されているように。
JavaScriptテストフレームワークの比較表
Jest、Mocha、Cypress、Playwrightなど。
- フレームワークは必須です。 最初の間違いは、ユニットテストを副次的な活動として扱うことです。その結果、不一致のファイル名、誰も覚えていないカスタム断言、しかも誰もが理解できないヘルパーが生まれます。
describeとtestまたはit - アサーション 読みやすいマッチャーとともに
- フック セットアップと破棄のために
- 非同期サポート プロミスとタイマー用
- モッキングツール 外部依存性用
あなたのチームも、ユニットレベルの作業を超えたテスト自動化のより広い視点が必要なら、Capgoはアプリ配信ワークフローの自動テストについて便利な概要を提供しています。 Capacitor.
Jest vs Mocha at a glance
JestとMochaは、2つの異なる哲学を表しています。
Jest は、ほとんどのチームが最初の日から必要とするものをすべて含むオールインワンオプションです。
Mocha は、ランナーを提供し、残りのスタックを組み立てることをチームに期待しています。
| 機能 | Jest | Mocha |
|---|---|---|
| セットアップの複雑さ | ほとんどのチームでは低くなります | 通常、アサーションとモッキングライブラリを追加するため、より高くなります |
| 確認 | 標準機能 | 通常は別のライブラリと組み合わせて使用 |
| モック | 標準機能 | 通常は別のライブラリと組み合わせて使用 |
| 非同期テスト | 標準機能で簡単 | サポートされているが、周囲の設定に依存する |
| カバレッジ ワークフロー | 通常は同じツールチェーンに組み込まれる | 多くの場合、別々に組み立てられる |
| 最適な選択 | 新しいプロジェクト、統一性を求めるチーム | 既存のスタック、モジュラーな制御を求めるチーム |
実践的なルール チームが、ランナーと組み合わせるべきアサーションライブラリとモッキングライブラリを尋ねる必要がある場合、Jestを使用したいと思います。
大多数のチーム向けの推奨事項
大多数の現代的なプロジェクトの場合、Mochaを使用する理由がすでに存在する場合を除いて、Jestを選択することをお勧めします。 Jest Mochaを使用する理由がすでに存在する場合を除いて、Jestを使用することをお勧めします。 Capacitor __CAPGO_KEEP_0__ または、既存のプロジェクトは十分な動的要素を持っているため、テストツールのスプレッドを削減することはすぐに効果を発揮します。
古いNode.jsサービスまたは長期間のコードベースでMochaがまだ意味をなす場合があります。ただし、mid-levelエンジニアが最初からから強力なテストスイートを設定する場合、Jestはより多くの摩擦を生み出すことよりも、より多くの摩擦を削減します。
CypressとPlaywrightは優れたツールですが、異なる問題を解決します。ブラウザレベルとエンドツーエンドのチェックに適していますが、ユニットテストのJavaScriptの作業が行われる高速な内部ループではありません。
プロジェクトの設定と最初のテスト
クリーンなテスト設定は、面白くないものでなければなりません。最初のテストを追加することが複雑な場合、スイートは健康に維持されない可能性があります。

シンプルなJestの設定
JavaScriptプロジェクトがすでにある場合から始めます。 package.json次に、Jestを開発依存モジュールとして追加し、テストスクリプトを設定します。
{
"scripts": {
"test": "jest"
}
}
これで多くのプロジェクトでは十分です。モジュールシステム、トランスピラーション、モノレポ構造が必要な場合は、後で追加することができます。
ローカルでCapacitorアプリケーションを構築中で、開発環境を整える前に共有ロジックの周りでテストを追加したい場合は、Capgoの「Capacitorローカル環境の設定」のガイドを参照してください。 Capacitor は実践的な相棒です。
codeのテストを書く前にテストを書いてください
テストの最初のパターンは、単に個人的な好みではありません。米国消費者金融保護局のJavaScriptガイドラインでは、テストを最初に書くことを明示的に推奨しています 最初にテストを書くことは重要ですテストを describe と itで expect(...) のチェックをフレームする のアサーションに.
That matters because test-first changes how you design code. Functions tend to become smaller, dependencies become more visible, and side effects stop leaking into logic that should stay pure.
それが重要なのは、テストの最初の変更が、__CAPGO_KEEP_0__の設計をどのように変えるかということです。関数は小さくなり、依存関係は明確になり、純粋でなければならないロジックに影響を与える副作用が止まります。
// 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);
});
});
最小限の例はこちらです。
The Arrange, Act, Assert pattern keeps tests readable, even when they grow more complex.
- Arrange the input and any needed setup.
- Act by calling the function.
- Applied to a validation helper: Small tests age well. A test should usually answer one question, not narrate an entire workflow.
For __CAPGO_KEEP_0__とElectronプロジェクトの場合、 disciplineはより重要です。純粋なロジックは、nativeまたはdesktopの統合__CAPGO_KEEP_1__と一緒にあります。ビジネスルールのテストはプラットフォームの実行時間なしで実行できるようにし、最初のテストが最後の有用なテストになるようにします。
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);
});
});
__CAPGO_KEEP_0__
For Capacitor and Electron projects, that discipline matters more because your pure logic often sits next to native or desktop integration code. Keep the business rule testable without the platform runtime, and your first test won’t be your last useful one.
Mastering Mocks and Asynchronous Code
アプリケーション code 内の多くのバグは、2 つの数字を加算することから来るものではありません。実際は、自身を超えて外部にアクセスする code から来るものです:ネットワークリクエスト、ファイル、プラグインAPI、タイマー、IPC チャネル、ストレージレイヤー。
テストは、codeの決定を中心に、境界の制御を与えることで、モッキングが役に立つ。

境界を模擬せず、すべてを信じない
テストの保守性を重視したガイドラインは、 単一行動カバレッジ と 1つの強力なアサーションをテストあたり1つ、モックを多用するとテストが脆弱になり、実装詳細に依存するようになるため、以下の要点がまとめられています。 TestRailの記事:維持可能なユニットテスト.
JavaScriptにおける警告は非常に重要です。チームは、すべてのインポートされたモジュールをモックすることで始めますが、実際の動作をテストするのではなく、関数が他の関数を正しく呼び出すかどうかをテストすることになります。
不適切なモックのテスト対象
- ヘルパーAがヘルパーBを呼び出しているかどうか
- サービスCがシリアライザーDを呼び出しているかどうか
- 内部のプライベート関数が2回実行されたかどうか
より適切なテスト対象
- 関数が何を返したか
- 依存関係が失敗した場合に正しく処理されたかどうか
- データを期待どおりに変換したかどうか
CapacitorとElectroncodeのよりよいパターン
モバイルアプリとデスクトップアプリでは、ネイティブまたはプラットフォーム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にも同様のパターンが機能します。ネイティブまたはプラットフォームAPIのラッパー層を作成し、ラッパーをモックすることでユニットテストを行うことができます。 ipcRendererファイルアクセスやシェル統合を、薄いアダプターの後ろに隠す。ユニットテストはサービス層に当たるが、実行時には直接当たらない。
チームがリリースロジックやアップデートパスのテストを行う場合、Capacitor アプリの場合、Capgo は関連するガイドを提供しています。 Capacitor のOTAアップデートをシナリオモックでテストする方法について.
アシンクロナステストスタイルを正常化するチームのために、簡単なウォーキースが役立ちます。
非同期フローをフラッキーのないようにテストする
テストで使用する場合、__CAPGO_KEEP_0__ がプロミスを返す場合、Promiseを使用します。コールバックヘビーなパターンよりも明確で、デバッグも容易です。 async/await in tests when the code under test returns a promise. It’s clearer than callback-heavy patterns and easier to debug.
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');
});
堅牢なテストの高度な戦略
テストスイートは、__CAPGO_KEEP_0__ が変更された後でも有用なままになることが重要です。それは、単にパスするテストを大量に書くことよりも難しいです。
A test suite becomes useful when it stays useful after the code changes. That’s harder than writing a pile of passing tests.

テスト分割を予算として使用してください
1つの実用的なガイドでは、 70/20/10 単位、統合、エンドツーエンドテストの 単位テストは最速のフィードバックと最も安定したエラーを提供します。同様のガイドは、完全な単位スイートが理想的には10秒以内に完了し、pre-commitチェックは5秒以内に完了することを述べています。このOpenReplayテストガイド 私はそれを予算ツールとしてではなく、宗教として扱います。エンドツーエンドテストに多くの努力を費やすと、チームはフィードバックを待ちすぎます。単位のみの場合は、システムの境界を実際に無視します。CapgoまたはElectronアプリの場合、健康的なバランスは通常以下のようになります: __CAPGO_KEEP_0____CAPGO_KEEP_0__ __CAPGO_KEEP_0__.
__CAPGO_KEEP_0__
Capacitor
- 単位テスト 価格ロジック、権限ルール、シリアライズ、更新資格、機能フラグ、状態変換のためのテスト
- 統合テスト ストレージアダプタ、プラグインラッパー、IPC契約のためのテスト
- E2Eテスト ログイン、購入フロー、同期、または更新ポップアップなどの重要なジャーニーの一部
カバレッジはフラッシュライト、目標はありません
カバレッジレポートは、重要なロジックの未テストブランチを発見するのに役立ちますが、チームがカバレッジパーセントを追求することで害を及ぼす場合もあります。
ログイン検証器に思いやりのあるエッジケーステストを実行すると、重要なUIの検証を強化するチームにとって、カバレッジが高いファイルに多くの無駄なアサーションがある場合よりも価値があります。特に、フォーム、パーサー、日付ロジック、許可チェックなどの入力が多いcodeの場合です。 検証が重視されるUIの品質を強化するチームにとって、このガイド フロントエンドフォーム検証のマスター
は単位レベルのテスト戦略の補完として役立ちます。
Aの内部をリファクタリングすることができるようにするには、テストの半分を書き直さなければならないことは避けたい。そこに到達する最も簡単な方法は、実装の詳細ではなく、観察可能な動作をアサートすることである。 観察可能な動作 実装の詳細ではなく
長期に持続するユースケース:
- 境界条件 空の入力、nullのような値、無効な型、 oversized文字列
- ドメインの結果 例えば、「許可が欠如しているため返却されました」
- 状態の移行 例えば、「ダウンロードメタデータが検証された後、更新をマークする」
ユースケースがよく腐るもの:
- 内部のヘルパー関数の呼び出しを検査する
- asserting private method sequencing
- mocking every layer in the call chain
For app teams building disciplined release processes, Capgo’s article on app quality assurance is useful because it connects testing work to the broader release pipeline.
Testing for CI, Capacitor, and Electron Apps
A test that only runs on one developer’s machine isn’t a safety net. It’s a local habit.
CI turns unit tests JavaScript work into team infrastructure. Every push, pull request, or release branch can exercise the same commands with the same expectations. That consistency matters even more for Capacitor and Electron projects, where environment drift causes subtle failures.
Make CI the default execution path
At minimum, your CI should install dependencies and run the unit suite on every change set. Keep the command identical to local development when possible.
A basic GitHub Actions workflow can be as small as this:
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
That’s enough to catch broken imports, failing assertions, and accidental platform assumptions before they land in main.
For mobile teams shipping through automated pipelines, Capgo has a practical guide to setting up CI/CD for Capacitor apps.
Testing Capacitor plugin interactions
The wrong way to unit test Capacitor code is to pull native plugins directly into every service. That couples your test suite to the platform bridge.
The better pattern is a thin abstraction:
// 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 });
});
The same idea applies to camera access, biometric prompts, push-token registration, and network status. Keep plugin calls in adapters. Test app logic against interfaces you control.
Testing Electron main renderer and IPC code
Electron apps have two important seams: main process code and renderer process code. Don’t blur them in tests.
A reliable setup usually separates:
- Renderer unit tests for view models, state, formatting, and UI-side business logic
- Main process unit tests for menus, file operations, and app lifecycle decisions
- IPC contract tests for message shape and expected responses
Example IPC wrapper:
// 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' });
});
If you later change the internal implementation from one helper to another, this test still holds because it verifies the behavior that matters. That’s the standard you want across desktop and mobile code.
Frequently Asked Questions About JavaScript Unit Testing
What’s the difference between unit integration and E2E tests
A 単体テスト 単体テストは、特定の論理的な小さな部分を孤立して確認します。 統合テスト 統合テストでは、複数のコンポーネントやサービスが正しく機能するかどうかを確認します。 エンドツーエンドテスト エンドツーエンドテストでは、実行中のアプリケーションを通してユーザーのジャーニーを実行します。
単体テストを使用してビジネスルールに対する迅速な信頼を確立すること。統合テストを使用して、ストレージ、プラグインラッパー、IPCなどのシームズを検証すること。E2Eテストを慎重に使用して、破損が深刻なワークフローを検証すること。
完全なカバレッジを目指すべきか
いいえ。完全なカバレッジは、チームを低価値のテストに導く可能性があります。
カバレッジは、リスクのあるcodeを明らかにするのに役立ちますが、エンジニアが浅いアサーションを追加してダッシュボードを満たすためにしか役に立たないことはありません。スイートが脆弱であれば、カバレッジが増加してもそれを救うことはできません。
既存のコードベースにテストを追加するにはどうすればよいですか
変更がすでに発生している場所から始めましょう。チームを凍結せずに、テスト戦略の大規模なリライトを発表しません。
A practical sequence looks like this:
- 保護中のcodeを先に保護する 機能開発やバグ修正の際に触れるモジュールにテストを追加する
- ハードウェアの難しいファイルから純粋なロジックを抽出する フレームワークやランタイムのノイズをなくしてビジネスルールをテストできるようにする
- ネイティブプラグイン、ネットワーククライアント、ファイルシステムの呼び出し、Electron IPCの周りにシームワッパーを追加する 脆弱なパターンを拒否する
- JavaScriptテストのベストプラクティスから指導を受ける 特に、オーバーモッキングとその後に続く脆弱なテストをよく見落とす問題を強調する 目標は即時的な完全性ではなく、チームにとって最もコストがかかる場所で徐々に改善すること __CAPGO_KEEP_0__
__CAPGO_KEEP_0__
If your team ships Capacitor or Electron apps and needs a cleaner release process around JavaScript changes, Capgo is one option to look at. It provides live updates for CapacitorJS and Electron apps, with rollout controls and observability, so teams can pair solid unit testing with a safer path to shipping web bundle changes without waiting on store review for every fix.