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 }