Jest 手動(dòng)模擬

2021-09-18 20:25 更新

手動(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')?是必需的。

模擬節(jié)點(diǎn)模塊

如果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ì)被模擬。

例子

  1. .
  2. ├── config
  3. ├── __mocks__
  4. │ └── fs.js
  5. ├── models
  6. │ ├── __mocks__
  7. │ │ └── user.js
  8. │ └── user.js
  9. ├── node_modules
  10. └── 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?模塊。

  1. // FileSummarizer.js
  2. 'use strict';
  3. const fs = require('fs');
  4. function summarizeFilesInDirectorySync(directory) {
  5. return fs.readdirSync(directory).map(fileName => ({
  6. directory,
  7. fileName,
  8. }));
  9. }
  10. 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 的自定義版本:

  1. // __mocks__/fs.js
  2. 'use strict';
  3. const path = require('path');
  4. const fs = jest.createMockFromModule('fs');
  5. // This is a custom function that our tests can use during setup to specify
  6. // what the files on the "mock" filesystem should look like when any of the
  7. // `fs` APIs are used.
  8. let mockFiles = Object.create(null);
  9. function __setMockFiles(newMockFiles) {
  10. mockFiles = Object.create(null);
  11. for (const file in newMockFiles) {
  12. const dir = path.dirname(file);
  13. if (!mockFiles[dir]) {
  14. mockFiles[dir] = [];
  15. }
  16. mockFiles[dir].push(path.basename(file));
  17. }
  18. }
  19. // A custom version of `readdirSync` that reads from the special mocked out
  20. // file list set via __setMockFiles
  21. function readdirSync(directoryPath) {
  22. return mockFiles[directoryPath] || [];
  23. }
  24. fs.__setMockFiles = __setMockFiles;
  25. fs.readdirSync = readdirSync;
  26. module.exports = fs;

現(xiàn)在我們編寫(xiě)我們的測(cè)試。請(qǐng)注意,我們需要明確說(shuō)明我們要模擬該?fs?模塊,因?yàn)樗且粋€(gè)核心 Node 模塊:

  1. // __tests__/FileSummarizer-test.js
  2. 'use strict';
  3. jest.mock('fs');
  4. describe('listFilesInDirectorySync', () => {
  5. const MOCK_FILE_INFO = {
  6. '/path/to/file1.js': 'console.log("file1 contents");',
  7. '/path/to/file2.txt': 'file2 contents',
  8. };
  9. beforeEach(() => {
  10. // Set up some mocked out file info before each test
  11. require('fs').__setMockFiles(MOCK_FILE_INFO);
  12. });
  13. test('includes all files in the directory in the summary', () => {
  14. const FileSummarizer = require('../FileSummarizer');
  15. const fileSummary = FileSummarizer.summarizeFilesInDirectorySync(
  16. '/path/to',
  17. );
  18. expect(fileSummary.length).toBe(2);
  19. });
  20. });

此處顯示的示例模擬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)入一起使用

如果你使用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 中實(shí)現(xiàn)的模擬方法

如果某些代碼使用了 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)題:

  1. Object.defineProperty(window, 'matchMedia', {
  2. writable: true,
  3. value: jest.fn().mockImplementation(query => ({
  4. matches: false,
  5. media: query,
  6. onchange: null,
  7. addListener: jest.fn(), // deprecated
  8. removeListener: jest.fn(), // deprecated
  9. addEventListener: jest.fn(),
  10. removeEventListener: jest.fn(),
  11. dispatchEvent: jest.fn(),
  12. })),
  13. });

如果?window.matchMedia()?在測(cè)試中調(diào)用的函數(shù)(或方法)中使用,則此方法有效。如果?window.matchMedia()?直接在測(cè)試文件中執(zhí)行,Jest 也會(huì)報(bào)同樣的錯(cuò)誤。在這種情況下,解決方案是將手動(dòng)模擬移動(dòng)到一個(gè)單獨(dú)的文件中,并在測(cè)試文件之前將其包含在測(cè)試中:

  1. import './matchMedia.mock'; // Must be imported before the tested file
  2. import {myMethod} from './file-to-test';
  3. describe('myMethod()', () => {
  4. // Test the method here...
  5. });


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)