jest 是如何 mock 掉模塊的

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
}
相關文章
相關標籤/搜索