Install any skill in seconds. Free to start, no credit card required.
Get Started Free →使用 Jest 模擬 Node.js 內建模組(如 fs、path 等)的技能。當測試需要隔離檔案系統操作或進行安全的測試時使用此技能。啟動條件:(1) Mock fs 模組 / Mock fs module (2) Jest Mock 檔案系統 / Jest Mock file system (3) 模擬 Node.js 內建模組 / Mock Node.js built-in modules (4) jest.mock 使用教學 / jest.mock usage guide (5) 使用 memfs-extra 模擬 fs / Use memfs-extra to mock fs (6) 記憶體檔案系統測試 / In-memory file system testing (7) Mock fs 測試 / Mock fs test (8) 隔離檔案系統測試 / Isolated file system testing (9) 安全的檔案系統操作 / Safe file system operations (10) Jest 虛擬檔案 / Jest virtual file syst
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 67% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 65% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 93% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 143% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 88% | 0% |
本技能提供使用 Jest 模擬 Node.js 內建模組的完整指南,特別是針對 fs 檔案系統模組的 Mock 技術。這對於在測試環境中隔離檔案系統操作、避免污染真實檔案系統非常重要。
This skill provides a complete guide to mocking Node.js built-in modules using Jest, especially for the fs file system module. This is crucial for isolating file system operations in test environments and avoiding pollution of the real file system.
在測試中直接操作真實檔案系統會導致以下問題:
Using real file system in tests can cause:
透過在 __mocks__ 資料夾中建立模擬檔案來實現。這是一種全域性的模擬方式,適合於整個專案多個測試檔案都需要模擬 fs 的情況。
This method is implemented by creating a mock file in the __mocks__ folder. It is a global mocking approach suitable for scenarios where multiple test files across the project need to mock fs.
步驟 1:建立模擬檔案
建立 test/__mocks__/fs.js(注意:必須是 .js 副檔名才有效):
javascript// test/__mocks__/fs.js module.exports = require('memfs-extra/fs-extra');
步驟 2:在測試檔案中啟用模擬
typescript// test/some-feature.spec.ts import fs from 'fs'; // 啟動模擬(放在 import 之後) jest.mock('fs'); describe('Some Feature', () => { it('should read file from memory', () => { // 現在 fs 是記憶體中的虛擬檔案系統 expect(fs).toHaveProperty('readJSON'); }); });
此方法在個別測試檔案中直接定義模擬行為。它提供了更高的靈活性,允許你針對特定測試檔案自定義模擬內容。
This method defines the mock behavior directly within individual test files. It provides higher flexibility, allowing you to customize the mock for specific test files.
直接在使用測試檔案的頂部使用 jest.mock 並提供工廠函式:
typescript// test/some-feature.spec.ts import fs from 'fs'; // Mock fs 模組 jest.mock('fs', () => { return require('memfs-extra/fs-extra'); }); // Mock fs/promises 子模組 jest.mock('fs/promises', () => { return require('memfs-extra/fs-extra').promises; }); describe('Some Feature', () => { it('should read file from memory', () => { expect(fs).toHaveProperty('readJSON'); }); });
fs 的測試檔案fs/promises)fstypescript// test/__mocks__/fs.js module.exports = require('memfs-extra/fs-extra');
typescript// test/file-service.spec.ts // @noUnusedParameters:false import fs from 'fs'; import { getVolumeFromFs } from 'memfs-extra'; // 啟動模擬 jest.mock('fs'); describe('FileService', () => { // 驗證 mock 是否成功,失敗時會拋出錯誤 const vol = getVolumeFromFs(fs); expect(vol).toBeDefined(); it('should read JSON file', () => { const testData = { name: 'test' }; // 寫入虛擬檔案 fs.writeFileSync('/test/data.json', JSON.stringify(testData)); // 讀取虛擬檔案 const result = fs.readJSONSync('/test/data.json'); expect(result).toEqual(testData); }); });
typescript// test/file-service-inline.spec.ts // @noUnusedParameters:false import fs from 'fs'; import { getVolumeFromFs } from 'memfs-extra'; // Mock fs 和 fs/promises jest.mock('fs', () => require('memfs-extra/fs-extra')); jest.mock('fs/promises', () => require('memfs-extra/fs-extra').promises); describe('FileService (Inline)', () => { // 驗證 mock 是否成功,失敗時會拋出錯誤 const vol = getVolumeFromFs(fs); expect(vol).toBeDefined(); it('should write and read JSON', () => { const testData = { name: 'test', value: 123 }; fs.writeJSONSync('/test/data.json', testData); const result = fs.readJSONSync('/test/data.json'); expect(result).toEqual(testData); }); it('should handle async operations', async () => { await fs.promises.writeFile('/test/async.txt', 'hello'); const content = await fs.promises.readFile('/test/async.txt', 'utf-8'); expect(content).toBe('hello'); }); });
| 特性 / Feature | Share Mock | Inline Mock | | :--- | :--- | :--- | | 定義位置 / Location | __mocks__/fs.js | 測試檔案內 / Inside test file | | 影響範圍 / Scope | 全域 / 多個檔案 | 單一檔案 | | 配置複雜度 / Complexity | 低(一次性配置) | 中(每個檔案需寫一次) | | 靈活性 / Flexibility | 低 | 高 | | 子模組支援 / Sub-module support | 需額外配置 | 可分別 mock |
typescriptjest.mock('fs', () => require('memfs-extra/fs-extra')); jest.mock('fs/promises', () => require('memfs-extra/fs-extra').promises); jest.mock('path', () => require('path'));
⚠️ 警告:除非有必要才使用這個做法
此方法會覆寫 memfs-extra 的原生行為,可能導致預期外的問題。建議優先使用下方的「虛擬檔案結構設定」方法。
typescript// ⚠️ 僅在有特殊需求時使用 jest.mock('fs', () => { const memfs = require('memfs-extra/fs-extra'); // 自定義行為 const customFs = { ...memfs, // 覆寫特定方法 readFileSync: jest.fn((path) => { if (path.includes('protected')) { throw new Error('Access denied'); } return memfs.readFileSync(path); }), }; return customFs; });
使用 getVolumeFromFs 取得的 Volume 物件來設定虛擬檔案結構,這是設定測試資料的推薦方式:
typescriptimport fs from 'fs'; import { getVolumeFromFs } from 'memfs-extra'; jest.mock('fs', () => require('memfs-extra/fs-extra')); describe('Virtual File Structure', () => { // 驗證 mock 是否成功 const vol = getVolumeFromFs(fs); expect(vol).toBeDefined(); it('should setup virtual file structure', () => { // 方法一:使用 mkdirSync 建立目錄 vol.mkdirSync('/test-dir'); vol.writeFileSync('/test-dir/file.txt', 'content'); // 方法二:使用 appendFileSync 新增檔案 vol.appendFileSync('/another-file.txt', 'hello'); // 方法三:使用 fromJSON 一次設定多個檔案(推薦) vol.fromJSON({ '/config.json': JSON.stringify({ name: 'test' }), '/data/users.json': JSON.stringify([{ id: 1 }]), '/logs/app.log': '2024-01-01 INFO: Started\n', }); // 驗證檔案存在 expect(fs.existsSync('/config.json')).toBe(true); expect(fs.existsSync('/data/users.json')).toBe(true); }); });
Volume API 常用方法:
| 方法 | 說明 | |------|------| | vol.mkdirSync(path) | 建立目錄 | | vol.writeFileSync(path, content) | 寫入檔案 | | vol.appendFileSync(path, content) | 追加檔案內容 | | vol.fromJSON(object) | 從物件建立多個檔案 | | vol.readFileSync(path) | 讀取檔案 | | vol.rmSync(path, { recursive: true }) | 刪除檔案/目錄 |
當需要同時使用虛擬檔案系統和真實檔案時:
typescriptimport * as fs from 'fs'; import * as realFs from 'fs'; jest.mock('fs', () => { const memfs = require('memfs-extra/fs-extra'); return { ...memfs, // 保持真實 fs 的某些方法 existsSync: realFs.existsSync, }; });
⚠️ 重要:即使有 mock 檔案系統,仍須遵守路徑安全原則
即使使用 memfs-extra 模擬了 fs 模組,仍應確保所有檔案操作都限制在測試臨時目錄內,避免路徑有機會離開臨時目錄範圍。
禁止使用的路徑模式:
/、C:\)正確的做法:
typescript// __root.ts import { join } from 'path'; /** 專案根目錄 / Project root directory */ export const __ROOT = join(__dirname, '..'); /** 測試目錄路徑 / Test directory path */ export const __ROOT_TEST = join(__ROOT, 'test'); /** 測試臨時目錄路徑 / Test temporary directory path */ export const __ROOT_TEST_TEMP = join(__ROOT_TEST, 'temp');
typescriptimport { join } from 'path'; import { __ROOT_TEST_TEMP } from '../__root'; // ✅ 正確:使用測試臨時目錄 const testSettingsPath = join(__ROOT_TEST_TEMP, 'mock/settings'); const testSettingsJsonPath = join(testSettingsPath, 'settings.json'); // ❌ 錯誤:使用根路徑 const unsafePath = '/test/config.json';
typescriptimport fs from 'fs'; import { getVolumeFromFs } from 'memfs-extra'; import { __ROOT_TEST_TEMP } from '../__root'; jest.mock('fs', () => require('memfs-extra/fs-extra')); const vol = getVolumeFromFs(fs); // ✅ 正確:使用臨時目錄相對路徑(在虛擬環境中) vol.mkdirSync(join(__ROOT_TEST_TEMP, 'mock')); vol.writeFileSync(join(__ROOT_TEST_TEMP, 'mock/settings.json'), '{}'); // ❌ 錯誤:使用根路徑(可能覆蓋真實系統檔案) vol.writeFileSync('/etc/config.json', '{}');
typescriptdescribe('Safe File Operations', () => { const testDir = '/test/temp'; beforeEach(() => { // 每個測試前清空虛擬目錄 if (fs.existsSync(testDir)) { fs.rmSync(testDir, { recursive: true }); } }); it('should isolate test operations', () => { fs.mkdirSync(testDir, { recursive: true }); fs.writeFileSync(`${testDir}/test.txt`, 'content'); expect(fs.existsSync(`${testDir}/test.txt`)).toBe(true); }); });
.js 副檔名?Jest 的自動 mock 機制需要 .js 副檔名才能正確識別模擬模組。
使用相同的方式:
typescriptjest.mock('path', () => require('path')); jest.mock('os', () => require('os'));
將 jest.mock('fs') 放在測試檔案的頂部,確保在任何測試執行前就已載入。
Other measured skills in the registry, with their headline benchmark lift.