手動(dòng)模擬用于通過(guò)模擬數(shù)據(jù)來(lái)剔除功能。例如,你可能希望創(chuàng)建一個(gè)允許使用假數(shù)據(jù)的手動(dòng)模擬,而不是訪問(wèn)像網(wǎng)站或數(shù)據(jù)庫(kù)這樣的遠(yuǎn)程資源。這將確保你的測(cè)試速度快,而且不會(huì)出現(xiàn)問(wèn)題。
手動(dòng)模擬是通過(guò)在?__mocks__/
?緊鄰模塊的子目錄中編寫(xiě)模塊來(lái)定義的。例如,mock 一個(gè)名為模塊?user
?的?models
?目錄下,創(chuàng)建一個(gè)名為?user.js
?,并把它的?models/__mocks__
?目錄中。請(qǐng)注意,?__mocks__
?文件夾區(qū)分大小寫(xiě),因此命名目錄?__MOCKS__
?在某些系統(tǒng)上會(huì)中斷。
當(dāng)我們需要在我們的測(cè)試,模塊,顯式調(diào)用?jest.mock('./moduleName')
?是必需的。
如果lodash
?模擬的模塊是 Node 模塊(例如:),則模擬應(yīng)放置在?__mocks__
?相鄰的目錄中?node_modules
?(除非你配置roots為指向項(xiàng)目根目錄以外的文件夾),并且會(huì)自動(dòng)模擬。無(wú)需顯式調(diào)用?jest.mock('module_name')
?.
可以通過(guò)在與作用域模塊名稱匹配的目錄結(jié)構(gòu)中創(chuàng)建文件來(lái)模擬作用域模塊。例如,要模擬名為@scope/project-name
?的作用域模塊?,請(qǐng)?jiān)?code>__mocks__/@scope/project-name.js?處創(chuàng)建一個(gè)文件?,并相應(yīng)地創(chuàng)建?@scope/
?目錄。
提示:如果我們想模擬節(jié)點(diǎn)的核心模塊(例如:?fs
?或?path
?),則顯式調(diào)用例如?jest.mock('path')
?是需要的,因?yàn)楹诵墓?jié)點(diǎn)模塊默認(rèn)情況下不會(huì)被模擬。
.
├── config
├── __mocks__
│ └── fs.js
├── models
│ ├── __mocks__
│ │ └── user.js
│ └── user.js
├── node_modules
└── views
當(dāng)給定模塊存在手動(dòng)模擬時(shí),Jest 的模塊系統(tǒng)將在顯式調(diào)用?jest.mock('moduleName')
?時(shí)使用該模塊. 但是,當(dāng)?automock
?設(shè)置為時(shí)?true
?,即使?jest.mock('moduleName')
?沒(méi)有調(diào)用,也會(huì)使用手動(dòng)模擬實(shí)現(xiàn)而不是自動(dòng)創(chuàng)建的模擬。要選擇退出此行為,需要顯式調(diào)用?jest.unmock('moduleName')
?應(yīng)使用實(shí)際模塊實(shí)現(xiàn)的測(cè)試。
注意:為了正確模擬,Jest 需要?jest.mock('moduleName')
?與?require/import
?語(yǔ)句在同一范圍內(nèi)。
這是一個(gè)人為的示例,其中我們有一個(gè)模塊,該模塊提供給定目錄中所有文件的摘要。在這種情況下,我們使用核心(內(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;
由于我們希望我們的測(cè)試避免實(shí)際訪問(wèn)磁盤(pán)(這非常緩慢和脆弱),我們fs通過(guò)擴(kuò)展自動(dòng)模擬為模塊創(chuàng)建了一個(gè)手動(dòng)模擬。我們的手動(dòng)模擬將實(shí)現(xiàn)?fs
?我們可以為測(cè)試構(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)在我們編寫(xiě)我們的測(cè)試。請(qǐng)注意,我們需要明確說(shuō)明我們要模擬該?fs
?模塊,因?yàn)樗且粋€(gè)核心 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用于生成自動(dòng)模擬,并覆蓋其默認(rèn)行為。這是推薦的方法,但完全是可選的。如果你根本不想使用自動(dòng)模擬,可以從模擬文件中導(dǎo)出自己的函數(shù)。完全手動(dòng)模擬的一個(gè)缺點(diǎn)是它們是手動(dòng)的——這意味著你必須在它們模擬的模塊發(fā)生變化時(shí)手動(dòng)更新它們。因此,最好在滿足你的需要時(shí)使用或擴(kuò)展自動(dòng)模擬。
為確保手動(dòng)模擬及其實(shí)際實(shí)現(xiàn)保持同步,要求jest.requireActual(moduleName)在手動(dòng)模擬中使用真實(shí)模塊并在導(dǎo)出之前使用模擬函數(shù)對(duì)其進(jìn)行修改可能很有用。
此示例的代碼可在examples/manual-mocks 中找到。
如果你使用ES 模塊導(dǎo)入,那么你通常會(huì)傾向于將你的?import
?語(yǔ)句放在測(cè)試文件的頂部。但通常你需要在模塊使用之前指示 Jest 使用模擬。出于這個(gè)原因,Jest 會(huì)自動(dòng)將?jest.mock
?調(diào)用提升到模塊的頂部(在任何導(dǎo)入之前)。要了解有關(guān)此內(nèi)容的更多信息并查看其實(shí)際效果,請(qǐng)參閱此內(nèi)容。
如果某些代碼使用了 JSDOM(Jest 使用的 DOM 實(shí)現(xiàn))尚未實(shí)現(xiàn)的方法,則很難對(duì)其進(jìn)行測(cè)試。這是例如與window.matchMedia()
?的情況?。Jest 返回?TypeError: window.matchMedia is not a function
?并且沒(méi)有正確執(zhí)行測(cè)試。
在這種情況下,?matchMedia
?在測(cè)試文件中進(jìn)行模擬應(yīng)該可以解決問(wèn)題:
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()
?在測(cè)試中調(diào)用的函數(shù)(或方法)中使用,則此方法有效。如果?window.matchMedia()
?直接在測(cè)試文件中執(zhí)行,Jest 也會(huì)報(bào)同樣的錯(cuò)誤。在這種情況下,解決方案是將手動(dòng)模擬移動(dòng)到一個(gè)單獨(dú)的文件中,并在測(cè)試文件之前將其包含在測(cè)試中:
import './matchMedia.mock'; // Must be imported before the tested file
import {myMethod} from './file-to-test';
describe('myMethod()', () => {
// Test the method here...
});
更多建議: