如何在mocha测试中模拟全局变量(define、module、window)

How to mock global variables (define, module, window) in mocha tests?

本文关键字:define module window 全局变量 模拟 mocha 测试      更新时间:2023-09-26

为了实现100%的代码覆盖率,我正尝试使用mocha来测试我的javascript模块在AMDCommonJS/Nodebrowser条件下是否正确加载。我使用的模式如下:

我的模块.js

(function(global){
  function MyClass(){}
  // AMD
  if(typeof define === 'function' && define.amd){
    define(function(){
      return MyClass;
    });
  // CommonJS/Node
  } else if (typeof module !== 'undefined' && module.exports){
    module.exports = MyClass;
  // Browser
  } else {
    global.MyClass = MyClass;
  }
})(this);

由于我使用node运行测试,所以从未定义define,始终定义module;因此"CommonJS/Node"条件是唯一经过测试的条件。

到目前为止,我尝试过的是这样的东西:

mymodule.test.js

var MyClass = require('./my-module');
describe('MyClass', function(){
  // suite of tests for the class itself
  // uses 'var instance = new MyClass();' in each test
  // all of these tests pass
});
describe('Exports', function(){
  // suite of tests for the export portion
  beforeEach(function(){
    MyClass = null; // will reload module for each test
    define = null; // set 'define' to null
    module = null; // set 'module' to null
  });
  // tests for AMD
  describe('AMD', function(){
    it('should have loaded as AMD module', function(){
      var define = function(){};
      define.amd = true;
      MyClass = require('./my-module'); // might be cached?
      // hoping this reloads with 'define' in its parent scope
      // but it does not. AMD condition is never reached.
      expect(spy).to.have.been.called(); // chai spy, code omitted
    });
  });
});

我正在使用间谍来检查是否调用了define,但该模块没有显示出任何重新加载define的迹象。我该如何实现这一点?

有没有一种安全的方法可以取消module,这样我也可以测试浏览器的条件?

您可能需要检查重新布线模块。我不能百分之百确定,但我认为它会让你做你需要的事情。

https://github.com/jhnns/rewire

我能够创建一个自定义解决方案(从http://howtonode.org/testing-private-state-and-mocking-deps)

模块加载器.js

这个模块基本上创建了一个新的上下文,在这个上下文中,您的自定义属性可以在与requireconsole等相同的全局空间中使用。

var vm = require('vm');
var fs = require('fs');
var path = require('path');
var extend = require('extend'); // install from npm
/**
 * Helper for unit testing:
 * - load module with mocked dependencies
 * - allow accessing private state of the module
 *
 * @param {string} filePath Absolute path to module (file to load)
 * @param {Object=} mocks Hash of mocked dependencies
 */
exports.loadModule = function(filePath, mocks) {
  mocks = mocks || {};
  // this is necessary to allow relative path modules within loaded file
  // i.e. requiring ./some inside file /a/b.js needs to be resolved to /a/some
  var resolveModule = function(module) {
    if (module.charAt(0) !== '.') return module;
    return path.resolve(path.dirname(filePath), module);
  };
  var exports = {};
  var context = {
    require: function(name) {
      return mocks[name] || require(resolveModule(name));
    },
    console: console,
    exports: exports,
    module: {
      exports: exports
    }
  };
  var extendMe = {};
  extend(true, extendMe, context, mocks);
  // runs your module in a VM with a new context containing your mocks
  // http://nodejs.org/api/vm.html#vm_vm_runinnewcontext_code_sandbox_filename
  vm.runInNewContext(fs.readFileSync(filePath), extendMe);
  return extendMe;
};

mymodule.test.js

var loadModule = require('./module-loader').loadModule;
// ...
it('should load module with mocked global vars', function(){
  function mockMethod(str){
    console.log("mock: "+str);
  }
  var MyMockModule = loadModule('./my-module.js', {mock:mockMethod});
  // 'MyClass' is available as MyMockModule.module.exports
});

我的模块.js

(function(global){
  function MyClass(){}
  if(typeof mock !== 'undefined'){ 
    mock("testing"); // will log "mock: testing"
  }
  module.exports = MyClass;
})(this);

这里有一个对我有用的轻量级解决方案:

let document = (typeof document === "undefined") ? {} : document;

不幸的是,这必须放在正在测试的文件中,对于使用更多document功能的情况来说会很麻烦,并且对于需要未定义的测试用例没有帮助。但对于简单的情况,这里有一个简单的解决方案。

(这是我发现这个问题时正在寻找的答案)