jest 里有两种 mock,一种是方法的 mock,还有一种是模块的 mock。这里我们来看一下模块的 mock 是如何实现的。
比如我们要 mock 掉 node 内置的 fs 模块,我们只要这么写:
const fs = require('fs');
jest.mock('fs');
console.log(fs.statSync('/tmp/file')); // undefined
jest 在执行这个文件的时候,首先会对代码进行转换,转换分成两步。
第一步是提升 jest.mock('fs')
,让它能作用在 require
之前,转换后的代码如下:
jest.mock('fs');
const fs = require('fs');
jest.mock('fs');
console.log(fs.statSync('/tmp/file'));
第二部是包一层匿名方法,这一步跟 node 的模块实现类似:
(function(module, exports, require, __dirname, __filename, global, jest){
jest.mock('fs');
const fs = require('fs');
console.log(fs.statSync('/tmp/file'));
}))
代码转换完后,jest 需要注入自己的 require 实现,这个一步通过让转换后的代码在 vm 模块创建的新的上下文里执行,最终生成一个可以执行的匿名方法实现。
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
的实现:
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
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。