Javascript 如何在mocha测试中模拟全局变量(定义、模块、窗口)?
为了实现100%的代码覆盖率,我尝试使用mocha测试我的javascript模块在Javascript 如何在mocha测试中模拟全局变量(定义、模块、窗口)?,javascript,node.js,unit-testing,mocha.js,Javascript,Node.js,Unit Testing,Mocha.js,为了实现100%的代码覆盖率,我尝试使用mocha测试我的javascript模块在AMD、CommonJS/Node和浏览器条件下是否正确加载。我使用的模式如下: my module.js (function(global){ function MyClass(){} // AMD if(typeof define === 'function' && define.amd){ define(function(){ return MyClass
AMD
、CommonJS/Node
和浏览器
条件下是否正确加载。我使用的模式如下:
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);
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
});
});
});
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
});
(function(global){
function MyClass(){}
if(typeof mock !== 'undefined'){
mock("testing"); // will log "mock: testing"
}
module.exports = MyClass;
})(this);
由于我使用节点运行测试,define
从未定义过,而module
始终是定义的;因此,“CommonJS/Node”条件是唯一一个经过测试的条件
到目前为止,我尝试的是这样的:
my module.test.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);
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
});
});
});
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
});
(function(global){
function MyClass(){}
if(typeof mock !== 'undefined'){
mock("testing"); // will log "mock: testing"
}
module.exports = MyClass;
})(this);
我正在使用spies检查是否调用了define
,但是模块没有显示任何迹象表明有可用的define
被重新加载。我怎样才能做到这一点
是否有一种安全的方法来取消
模块
,以便我也可以测试浏览器条件?您可能需要检查重新布线模块。我不是百分之百确定,但我想它会让你做你需要的
我能够创建一个定制的解决方案(借用了大部分代码) 模块加载器.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);
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
});
});
});
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
});
(function(global){
function MyClass(){}
if(typeof mock !== 'undefined'){
mock("testing"); // will log "mock: testing"
}
module.exports = MyClass;
})(this);
此模块基本上创建了一个新的上下文,在该上下文中,您的自定义属性可以在与require
和console
等相同的全局空间中使用
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;
};
my module.test.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);
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
});
});
});
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
});
(function(global){
function MyClass(){}
if(typeof mock !== 'undefined'){
mock("testing"); // will log "mock: testing"
}
module.exports = MyClass;
})(this);
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);
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
});
});
});
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
});
(function(global){
function MyClass(){}
if(typeof mock !== 'undefined'){
mock("testing"); // will log "mock: testing"
}
module.exports = MyClass;
})(this);
以下是一个对我有效的轻量级解决方案:
let document = (typeof document === "undefined") ? {} : document;
不幸的是,这必须放在被测试的文件中,对于使用更多文档
功能的情况来说,这会很麻烦,并且对于需要未定义内容的测试用例没有帮助。但对于简单的情况,这里有一个简单的解决方案
(这是我找到这个问题时一直在寻找的答案)谢谢你的回答!这看起来很有希望,明天将进行测试!有用的模块,但在这种特殊情况下没有雪茄。我想这可能是因为我需要包装IIFE可用的全局变量,而不是导出类本身。