最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to mock global variables (define, module, window) in mocha tests? - Stack Overflow

programmeradmin14浏览0评论

In the pursuit of 100% code coverage, I am attempting to use mocha to test that my javascript module is loading correctly under AMD, CommonJS/Node, and browser conditions. The pattern I'm using is below:

my-module.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);

Since I am running my tests with node, define is never defined, and module is always defined; so the "CommonJS/Node" condition is the only one that ever gets tested.

What I have tried so far is something like this:

my-module.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
    });
  });
});

I'm using spies to check if define has been called, but the module doesn't show any signs of ever being reloaded with define available to it. How can I achieve that?

And is there a safe way of nullifying module so that I can test the browser condition as well?

In the pursuit of 100% code coverage, I am attempting to use mocha to test that my javascript module is loading correctly under AMD, CommonJS/Node, and browser conditions. The pattern I'm using is below:

my-module.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);

Since I am running my tests with node, define is never defined, and module is always defined; so the "CommonJS/Node" condition is the only one that ever gets tested.

What I have tried so far is something like this:

my-module.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
    });
  });
});

I'm using spies to check if define has been called, but the module doesn't show any signs of ever being reloaded with define available to it. How can I achieve that?

And is there a safe way of nullifying module so that I can test the browser condition as well?

Share asked Feb 5, 2015 at 1:25 SteamDevSteamDev 4,4145 gold badges21 silver badges29 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 3

You might want to check out the rewire module. I'm not 100% sure, but I think it'll let you do what you need.

https://github./jhnns/rewire

I was able to create a custom solution (borrowing the majority of this code from http://howtonode/testing-private-state-and-mocking-deps)

module-loader.js

This module basically creates a new context in which your custom properties are available in the same global space as require and console, etc.

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/api/vm.html#vm_vm_runinnewcontext_code_sandbox_filename
  vm.runInNewContext(fs.readFileSync(filePath), extendMe);

  return extendMe;
};

my-module.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
});

my-module.js

(function(global){

  function MyClass(){}

  if(typeof mock !== 'undefined'){ 
    mock("testing"); // will log "mock: testing"
  }

  module.exports = MyClass;

})(this);

Here's a lightweight solution that worked for me:

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

Unfortunately, this has to go in the file being tested, would be cumbersome for cases where more of document's functionality is used, and doesn't help with needing test cases where something is undefined. But for simple cases, here's a simple solution.

(This is the answer I was looking for when I found this question)

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论