手動模擬用于通過模擬數(shù)據(jù)來剔除功能。例如,你可能希望創(chuàng)建一個允許使用假數(shù)據(jù)的手動模擬,而不是訪問像網(wǎng)站或數(shù)據(jù)庫這樣的遠(yuǎn)程資源。這將確保你的測試速度快,而且不會出現(xiàn)問題。
手動模擬是通過在?__mocks__/
?緊鄰模塊的子目錄中編寫模塊來定義的。例如,mock 一個名為模塊?user
?的?models
?目錄下,創(chuàng)建一個名為?user.js
?,并把它的?models/__mocks__
?目錄中。請注意,?__mocks__
?文件夾區(qū)分大小寫,因此命名目錄?__MOCKS__
?在某些系統(tǒng)上會中斷。
當(dāng)我們需要在我們的測試,模塊,顯式調(diào)用?jest.mock('./moduleName')
?是必需的。
如果lodash
?模擬的模塊是 Node 模塊(例如:),則模擬應(yīng)放置在?__mocks__
?相鄰的目錄中?node_modules
?(除非你配置roots為指向項目根目錄以外的文件夾),并且會自動模擬。無需顯式調(diào)用?jest.mock('module_name')
?.
可以通過在與作用域模塊名稱匹配的目錄結(jié)構(gòu)中創(chuàng)建文件來模擬作用域模塊。例如,要模擬名為@scope/project-name
?的作用域模塊?,請在__mocks__/@scope/project-name.js
?處創(chuàng)建一個文件?,并相應(yīng)地創(chuàng)建?@scope/
?目錄。
提示:如果我們想模擬節(jié)點的核心模塊(例如:?fs
?或?path
?),則顯式調(diào)用例如?jest.mock('path')
?是需要的,因為核心節(jié)點模塊默認(rèn)情況下不會被模擬。
.
├── config
├── __mocks__
│ └── fs.js
├── models
│ ├── __mocks__
│ │ └── user.js
│ └── user.js
├── node_modules
└── views
當(dāng)給定模塊存在手動模擬時,Jest 的模塊系統(tǒng)將在顯式調(diào)用?jest.mock('moduleName')
?時使用該模塊. 但是,當(dāng)?automock
?設(shè)置為時?true
?,即使?jest.mock('moduleName')
?沒有調(diào)用,也會使用手動模擬實現(xiàn)而不是自動創(chuàng)建的模擬。要選擇退出此行為,需要顯式調(diào)用?jest.unmock('moduleName')
?應(yīng)使用實際模塊實現(xiàn)的測試。
注意:為了正確模擬,Jest 需要?jest.mock('moduleName')
?與?require/import
?語句在同一范圍內(nèi)。
這是一個人為的示例,其中我們有一個模塊,該模塊提供給定目錄中所有文件的摘要。在這種情況下,我們使用核心(內(nèi)置)?fs
?模塊。
// FileSummarizer.js
'use strict';
const fs = require('fs');
function summarizeFilesInDirectorySync(directory) {
return fs.readdirSync(directory).map(fileName => ({
directory,
fileName,
}));
}
exports.summarizeFilesInDirectorySync = summarizeFilesInDirectorySync;
由于我們希望我們的測試避免實際訪問磁盤(這非常緩慢和脆弱),我們fs通過擴展自動模擬為模塊創(chuàng)建了一個手動模擬。我們的手動模擬將實現(xiàn)?fs
?我們可以為測試構(gòu)建的API 的自定義版本:
// __mocks__/fs.js
'use strict';
const path = require('path');
const fs = jest.createMockFromModule('fs');
// This is a custom function that our tests can use during setup to specify
// what the files on the "mock" filesystem should look like when any of the
// `fs` APIs are used.
let mockFiles = Object.create(null);
function __setMockFiles(newMockFiles) {
mockFiles = Object.create(null);
for (const file in newMockFiles) {
const dir = path.dirname(file);
if (!mockFiles[dir]) {
mockFiles[dir] = [];
}
mockFiles[dir].push(path.basename(file));
}
}
// A custom version of `readdirSync` that reads from the special mocked out
// file list set via __setMockFiles
function readdirSync(directoryPath) {
return mockFiles[directoryPath] || [];
}
fs.__setMockFiles = __setMockFiles;
fs.readdirSync = readdirSync;
module.exports = fs;
現(xiàn)在我們編寫我們的測試。請注意,我們需要明確說明我們要模擬該?fs
?模塊,因為它是一個核心 Node 模塊:
// __tests__/FileSummarizer-test.js
'use strict';
jest.mock('fs');
describe('listFilesInDirectorySync', () => {
const MOCK_FILE_INFO = {
'/path/to/file1.js': 'console.log("file1 contents");',
'/path/to/file2.txt': 'file2 contents',
};
beforeEach(() => {
// Set up some mocked out file info before each test
require('fs').__setMockFiles(MOCK_FILE_INFO);
});
test('includes all files in the directory in the summary', () => {
const FileSummarizer = require('../FileSummarizer');
const fileSummary = FileSummarizer.summarizeFilesInDirectorySync(
'/path/to',
);
expect(fileSummary.length).toBe(2);
});
});
此處顯示的示例模擬jest.createMockFromModule用于生成自動模擬,并覆蓋其默認(rèn)行為。這是推薦的方法,但完全是可選的。如果你根本不想使用自動模擬,可以從模擬文件中導(dǎo)出自己的函數(shù)。完全手動模擬的一個缺點是它們是手動的——這意味著你必須在它們模擬的模塊發(fā)生變化時手動更新它們。因此,最好在滿足你的需要時使用或擴展自動模擬。
為確保手動模擬及其實際實現(xiàn)保持同步,要求jest.requireActual(moduleName)在手動模擬中使用真實模塊并在導(dǎo)出之前使用模擬函數(shù)對其進(jìn)行修改可能很有用。
此示例的代碼可在examples/manual-mocks 中找到。
如果你使用ES 模塊導(dǎo)入,那么你通常會傾向于將你的?import
?語句放在測試文件的頂部。但通常你需要在模塊使用之前指示 Jest 使用模擬。出于這個原因,Jest 會自動將?jest.mock
?調(diào)用提升到模塊的頂部(在任何導(dǎo)入之前)。要了解有關(guān)此內(nèi)容的更多信息并查看其實際效果,請參閱此內(nèi)容。
如果某些代碼使用了 JSDOM(Jest 使用的 DOM 實現(xiàn))尚未實現(xiàn)的方法,則很難對其進(jìn)行測試。這是例如與window.matchMedia()
?的情況?。Jest 返回?TypeError: window.matchMedia is not a function
?并且沒有正確執(zhí)行測試。
在這種情況下,?matchMedia
?在測試文件中進(jìn)行模擬應(yīng)該可以解決問題:
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
如果?window.matchMedia()
?在測試中調(diào)用的函數(shù)(或方法)中使用,則此方法有效。如果?window.matchMedia()
?直接在測試文件中執(zhí)行,Jest 也會報同樣的錯誤。在這種情況下,解決方案是將手動模擬移動到一個單獨的文件中,并在測試文件之前將其包含在測試中:
import './matchMedia.mock'; // Must be imported before the tested file
import {myMethod} from './file-to-test';
describe('myMethod()', () => {
// Test the method here...
});
更多建議: