Javascript 如何在 RequireJS 中模拟单元测试的依赖项?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11439540/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I mock dependencies for unit testing in RequireJS?
提问by jergason
I have an AMD module I want to test, but I want to mock out its dependencies instead of loading the actual dependencies. I am using requirejs, and the code for my module looks something like this:
我有一个要测试的 AMD 模块,但我想模拟它的依赖项而不是加载实际的依赖项。我正在使用 requirejs,我的模块的代码如下所示:
define(['hurp', 'durp'], function(Hurp, Durp) {
return {
foo: function () {
console.log(Hurp.beans)
},
bar: function () {
console.log(Durp.beans)
}
}
}
How can I mock out hurp
and durp
so I can effectively unit test?
我怎样才能模拟出来hurp
,durp
这样我才能有效地进行单元测试?
回答by Andreas K?berle
So after reading this postI came up with a solution that use the requirejs config function to create a new context for your test where you can simply mock your dependencies:
所以在阅读这篇文章后,我想出了一个解决方案,它使用 requirejs 配置函数为您的测试创建一个新的上下文,您可以在其中简单地模拟您的依赖项:
var cnt = 0;
function createContext(stubs) {
cnt++;
var map = {};
var i18n = stubs.i18n;
stubs.i18n = {
load: sinon.spy(function(name, req, onLoad) {
onLoad(i18n);
})
};
_.each(stubs, function(value, key) {
var stubName = 'stub' + key + cnt;
map[key] = stubName;
define(stubName, function() {
return value;
});
});
return require.config({
context: "context_" + cnt,
map: {
"*": map
},
baseUrl: 'js/cfe/app/'
});
}
So it creates a new context where the definitions for Hurp
and Durp
will be set by the objects you passed into the function. The Math.random for the name is maybe a bit dirty but it works. Cause if you'll have a bunch of test you need to create new context for every suite to prevent reusing your mocks, or to load mocks when you want the real requirejs module.
因此,它创建了一个新的上下文,其中的定义Hurp
和Durp
将由您传递给函数的对象设置。名称的 Math.random 可能有点脏,但它有效。因为如果你有一堆测试,你需要为每个套件创建新的上下文以防止重用你的模拟,或者在你想要真正的 requirejs 模块时加载模拟。
In your case it would look like this:
在您的情况下,它看起来像这样:
(function () {
var stubs = {
hurp: 'hurp',
durp: 'durp'
};
var context = createContext(stubs);
context(['yourModuleName'], function (yourModule) {
//your normal jasmine test starts here
describe("yourModuleName", function () {
it('should log', function(){
spyOn(console, 'log');
yourModule.foo();
expect(console.log).toHasBeenCalledWith('hurp');
})
});
});
})();
So I'm using this approach in production for a while and its really robust.
所以我在生产中使用这种方法有一段时间了,它非常强大。
回答by busticated
you might want to check out the new Squire.js lib
您可能想查看新的Squire.js 库
from the docs:
从文档:
Squire.js is a dependency injector for Require.js users to make mocking dependencies easy!
Squire.js 是 Require.js 用户的依赖注入器,可以让模拟依赖变得容易!
回答by jergason
I have found three different solutions to this problem, none of them pleasant.
我为这个问题找到了三种不同的解决方案,但没有一个是令人愉快的。
Defining Dependencies Inline
内联定义依赖项
define('hurp', [], function () {
return {
beans: 'Beans'
};
});
define('durp', [], function () {
return {
beans: 'durp beans'
};
});
require('hurpdhurp', function () {
// test hurpdurp in here
});
Fugly. You have to clutter up your tests with lots of AMD boilerplate.
闷闷不乐。你必须用大量的 AMD 样板来打乱你的测试。
Loading Mock Dependencies From Different Paths
从不同路径加载模拟依赖
This involves using a separate config.js file to define paths for each of the dependencies that point to mocks instead of the original dependencies. This is also ugly, requiring the creation of tons of test files and configurations files.
这涉及使用单独的 config.js 文件为每个指向模拟而不是原始依赖项的依赖项定义路径。这也很丑陋,需要创建大量的测试文件和配置文件。
Fake It In Node
在节点中伪造它
This is my current solution, but is still a terrible one.
这是我目前的解决方案,但仍然是一个糟糕的解决方案。
You create your own define
function to provide your own mocks to the module and put your tests in the callback. Then you eval
the module to run your tests, like so:
您创建自己的define
函数来为模块提供自己的模拟并将您的测试放在回调中。然后你eval
的模块来运行你的测试,像这样:
var fs = require('fs')
, hurp = {
beans: 'BEANS'
}
, durp = {
beans: 'durp beans'
}
, hurpDurp = fs.readFileSync('path/to/hurpDurp', 'utf8');
;
function define(deps, cb) {
var TestableHurpDurp = cb(hurp, durp);
// now run tests below on TestableHurpDurp, which is using your
// passed-in mocks as dependencies.
}
// evaluate the AMD module, running your mocked define function and your tests.
eval(hurpDurp);
This is my preferred solution. It looks a little magic, but it has a few benefits.
这是我首选的解决方案。它看起来有点神奇,但它有一些好处。
- Run your tests in node, so no messing with browser automation.
- Less need for messy AMD boilerplate in your tests.
- You get to use
eval
in anger, and imagine Crockford exploding with rage.
- 在 node 中运行你的测试,所以不会搞乱浏览器自动化。
- 在您的测试中更少需要凌乱的 AMD 样板。
- 你可以
eval
在愤怒中使用,并想象克罗克福德怒火中烧。
It still has some drawbacks, obviously.
显然,它仍然有一些缺点。
- Since you are testing in node, you can't do anything with browser events or DOM manipulation. Only good for testing logic.
- Still a little clunky to set up. You need to mock out
define
in every test, since that is where your tests actually run.
- 由于您在 node 中进行测试,因此您无法对浏览器事件或 DOM 操作进行任何操作。只适合测试逻辑。
- 设置起来还是有点笨拙。您需要
define
在每个测试中模拟,因为这是您的测试实际运行的地方。
I am working on a test runner to give a nicer syntax for this kind of stuff, but I still have no good solution for problem 1.
我正在开发一个测试运行器,为这类东西提供更好的语法,但我仍然没有解决问题 1 的好方法。
Conclusion
结论
Mocking deps in requirejs sucks hard. I found a way that sortof works, but am still not very happy with it. Please let me know if you have any better ideas.
在 requirejs 中模拟 deps 很难。我找到了一种有效的方法,但仍然不太满意。如果您有更好的想法,请告诉我。
回答by Artem Oboturov
There's a config.map
option http://requirejs.org/docs/api.html#config-map.
有一个config.map
选项http://requirejs.org/docs/api.html#config-map。
On how-to use it:
关于如何使用它:
- Define normal module;
- Define stub module;
Configure RequireJS expicitely;
requirejs.config({ map: { 'source/js': { 'foo': 'normalModule' }, 'source/test': { 'foo': 'stubModule' } } });
- 定义普通模块;
- 定义存根模块;
显式配置RequireJS;
requirejs.config({ map: { 'source/js': { 'foo': 'normalModule' }, 'source/test': { 'foo': 'stubModule' } } });
In this case for normal and test code you could use the foo
module which will be real module reference and stub accordingly.
在这种情况下,对于正常和测试代码,您可以使用foo
模块,该模块将是真正的模块引用和相应的存根。
回答by janith
You can use testr.jsto mock dependencies. You can set testr to load the mock dependencies instead of the original ones. Here is an example usage:
您可以使用testr.js来模拟依赖项。您可以设置 testr 以加载模拟依赖项而不是原始依赖项。这是一个示例用法:
var fakeDep = function(){
this.getText = function(){
return 'Fake Dependancy';
};
};
var Module1 = testr('module1', {
'dependancies/dependancy1':fakeDep
});
Check out this as well: http://cyberasylum.janithw.com/mocking-requirejs-dependencies-for-unit-testing/
也看看这个:http: //cyberasylum.janithw.com/mocking-requirejs-dependencies-for-unit-testing/
回答by Domysee
This answer is based on Andreas K?berle's answer.
It wasn't that easy for me to implement and understand his solution, so I'll explain it in a bit more detail how it works, and some pitfalls to avoid, hoping that it will help future visitors.
此答案基于Andreas K?berle 的答案。
实施和理解他的解决方案对我来说并不容易,所以我会更详细地解释它是如何工作的,以及要避免的一些陷阱,希望它能帮助未来的访问者。
So, first of all the setup:
I'm using Karmaas test runner and MochaJsas test framework.
因此,首先进行设置:
我使用Karma作为测试运行程序,使用MochaJs作为测试框架。
Using something like Squiredidn't work for me, for some reason, when I used it, the test framework threw errors:
使用像Squire这样的东西对我不起作用,出于某种原因,当我使用它时,测试框架抛出了错误:
TypeError: Cannot read property 'call' of undefined
类型错误:无法读取未定义的属性“调用”
RequireJshas the possibility to mapmodule ids to other module ids. It also allows to create a require
functionthat uses a different configthan the global require
.
These features is crucial for this solution to work.
RequireJs可以将模块 id映射到其他模块 id。它还允许创建一个使用与 global不同的配置的require
函数。
这些功能对于此解决方案的工作至关重要。require
Here is my version of the mock code, including (a lot) comments (I hope its understandable). I wrapped it inside a module, so that the tests can easily require it.
这是我的模拟代码版本,包括(很多)注释(我希望它可以理解)。我将它包裹在一个模块中,以便测试可以轻松地需要它。
define([], function () {
var count = 0;
var requireJsMock= Object.create(null);
requireJsMock.createMockRequire = function (mocks) {
//mocks is an object with the module ids/paths as keys, and the module as value
count++;
var map = {};
//register the mocks with unique names, and create a mapping from the mocked module id to the mock module id
//this will cause RequireJs to load the mock module instead of the real one
for (property in mocks) {
if (mocks.hasOwnProperty(property)) {
var moduleId = property; //the object property is the module id
var module = mocks[property]; //the value is the mock
var stubId = 'stub' + moduleId + count; //create a unique name to register the module
map[moduleId] = stubId; //add to the mapping
//register the mock with the unique id, so that RequireJs can actually call it
define(stubId, function () {
return module;
});
}
}
var defaultContext = requirejs.s.contexts._.config;
var requireMockContext = { baseUrl: defaultContext.baseUrl }; //use the baseUrl of the global RequireJs config, so that it doesn't have to be repeated here
requireMockContext.context = "context_" + count; //use a unique context name, so that the configs dont overlap
//use the mapping for all modules
requireMockContext.map = {
"*": map
};
return require.config(requireMockContext); //create a require function that uses the new config
};
return requireJsMock;
});
The biggest pitfallI encountered, which literally cost me hours, was creating the RequireJs config. I tried to (deep) copy it, and only override the necessary properties (like context or map). This does not work! Only copy the baseUrl
, this works fine.
我遇到的最大陷阱(实际上花费了我数小时)是创建 RequireJs 配置。我试图(深度)复制它,并且只覆盖必要的属性(如上下文或地图)。这不起作用!只复制baseUrl
,这很好用。
Usage
用法
To use it, require it in your test, create the mocks, and then pass it to createMockRequire
. For example:
要使用它,需要在你的测试中使用它,创建模拟,然后将它传递给createMockRequire
. 例如:
var ModuleMock = function () {
this.method = function () {
methodCalled += 1;
};
};
var mocks = {
"ModuleIdOrPath": ModuleMock
}
var requireMocks = mocker.createMockRequire(mocks);
And here an example of a complete test file:
这是一个完整测试文件的示例:
define(["chai", "requireJsMock"], function (chai, requireJsMock) {
var expect = chai.expect;
describe("Module", function () {
describe("Method", function () {
it("should work", function () {
return new Promise(function (resolve, reject) {
var handler = { handle: function () { } };
var called = 0;
var moduleBMock = function () {
this.method = function () {
methodCalled += 1;
};
};
var mocks = {
"ModuleBIdOrPath": moduleBMock
}
var requireMocks = requireJsMock.createMockRequire(mocks);
requireMocks(["js/ModuleA"], function (moduleA) {
try {
moduleA.method(); //moduleA should call method of moduleBMock
expect(called).to.equal(1);
resolve();
} catch (e) {
reject(e);
}
});
});
});
});
});
});
回答by user3033599
if you want to make some plain js tests which isolate one unit, then you can simply use this snippet:
如果你想做一些简单的 js 测试来隔离一个单元,那么你可以简单地使用这个片段:
function define(args, func){
if(!args.length){
throw new Error("please stick to the require.js api which wants a: define(['mydependency'], function(){})");
}
var fileName = document.scripts[document.scripts.length-1].src;
// get rid of the url and path elements
fileName = fileName.split("/");
fileName = fileName[fileName.length-1];
// get rid of the file ending
fileName = fileName.split(".");
fileName = fileName[0];
window[fileName] = func;
return func;
}
window.define = define;