jest 裏有兩種 mock,一種是方法的 mock,還有一種是模塊的 mock。這裏咱們來看一下模塊的 mock 是如何實現的。javascript
好比咱們要 mock 掉 node 內置的 fs 模塊,咱們只要這麼寫:java
const fs = require('fs'); jest.mock('fs'); console.log(fs.statSync('/tmp/file')); // undefined
jest 在執行這個文件的時候,首先會對代碼進行轉換,轉換分紅兩步。node
第一步是提高 jest.mock('fs')
,讓它能做用在 require
以前,轉換後的代碼以下:ui
jest.mock('fs'); const fs = require('fs'); jest.mock('fs'); console.log(fs.statSync('/tmp/file'));
第二部是包一層匿名方法,這一步跟 node 的模塊實現相似:code
(function(module, exports, require, __dirname, __filename, global, jest){ jest.mock('fs'); const fs = require('fs'); console.log(fs.statSync('/tmp/file')); }))
代碼轉換完後,jest 須要注入本身的 require 實現,這個一步經過讓轉換後的代碼在 vm 模塊建立的新的上下文裏執行,最終生成一個能夠執行的匿名方法實現。ip
const vm = require('vm'); const code = '轉換後的代碼'; const script = new vm.Script(code); const result = script.runInContext(context); // describe, it 等全局方法在這裏注入 result.call( module, exports, require, // jest 本身的 require 實現, ... );
最後,咱們用僞代碼來描述下 require
的實現:get
const shouldMock = {}; function mock(moduleName) { // jest 會給每一個模塊生成一個 moduleId, 好比這裏是 `node:fs:` 表示這是一個 node 模塊 const moduleId = getModuleId(moduleName); shouldMock[moduleId] = true; } // 這個就是 jest 給咱們的代碼注入的 require 方法 function requireModuleOrMock(moduleName) { if(shouldMock(moduleName)) { return requireMockModule(moduleName); } else { return requireModule(moduleName); } } function shouldMock(moduleName) { const moduleId = getModuleId(moduleName); return moduleId in shouldMock; } function requireMockModule(moduleName) { const moduleExports = requireModule(moduleName); return Object.keys(moduleExports).reduce((mock, key) => { mock[key] = () => {}; // mock 的方法 return mock; }, {}) } function requireModule(moduleName) { return require(moduleName); // 這個是原始的 require }