热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

如何在RequireJS中模拟单元测试的依赖项?-HowcanImockdependenciesforunittestinginRequireJS?

IhaveanAMDmoduleIwanttotest,butIwanttomockoutitsdependenciesinsteadofloadingthe

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?

我怎么能模拟出来的冲击和durp所以我可以有效地进行单元测试?

7 个解决方案

#1


64  

So after reading this post I 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.

所以我在生产中使用这种方法已经有一段时间了,它非常强大。

#2


44  

you might want to check out the new Squire.js lib

你可能想看一下新的Squire.js lib

from the docs:

来自文档:

Squire.js is a dependency injector for Require.js users to make mocking dependencies easy!

Squire.js是Require.js用户的依赖注入器,可以轻松实现模拟依赖项!

#3


16  

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.

的fugly。你必须用大量的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:

您可以创建自己的定义函数,为模块提供自己的模拟,并将测试放入回调中。然后你评估模块以运行测试,如下所示:

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.

这是我的首选解决方案。它看起来有点神奇,但它有一些好处。

  1. Run your tests in node, so no messing with browser automation.
  2. 在节点中运行测试,因此不会弄乱浏览器自动化。
  3. Less need for messy AMD boilerplate in your tests.
  4. 在测试中不需要凌乱的AMD样板。
  5. You get to use eval in anger, and imagine Crockford exploding with rage.
  6. 你可以在愤怒中使用eval,想象一下Crockford会愤怒地爆发。

It still has some drawbacks, obviously.

显然,它仍然有一些缺点。

  1. Since you are testing in node, you can't do anything with browser events or DOM manipulation. Only good for testing logic.
  2. 由于您在节点中进行测试,因此无法对浏览器事件或DOM操作执行任何操作。仅适用于测试逻辑。
  3. Still a little clunky to set up. You need to mock out define in every test, since that is where your tests actually run.
  4. 设置仍然有点笨拙。您需要在每个测试中模拟出定义,因为这是您的测试实际运行的地方。

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很难。我找到了一种有效的方法,但我仍然不满意。如果您有任何更好的想法,请告诉我。

#4


15  

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:

关于如何使用它:

  1. Define normal module;
  2. 定义正常模块;
  3. Define stub module;
  4. 定义存根模块;
  5. Configure RequireJS expicitely;

    expicitely配置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模块,它将是相应的实模块引用和存根。

#5


9  

You can use testr.js to 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/

#6


2  

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.

这个答案基于AndreasKöberle的回答。对我来说实现和理解他的解决方案并不容易,因此我将更详细地解释它是如何工作的,以及一些要避免的陷阱,希望它能够帮助未来的访问者。

So, first of all the setup:
I'm using Karma as test runner and MochaJs as test framework.

所以,首先是设置:我使用Karma作为测试运行器,使用MochaJs作为测试框架。

Using something like Squire didn't work for me, for some reason, when I used it, the test framework threw errors:

使用像Squire这样的东西对我来说不起作用,出于某种原因,当我使用它时,测试框架会引发错误:

TypeError: Cannot read property 'call' of undefined

TypeError:无法读取未定义的属性“call”

RequireJs has the possibility to map module ids to other module ids. It also allows to create a require function that uses a different config than the global require.
These features is crucial for this solution to work.

RequireJs可以将模块ID映射到其他模块ID。它还允许创建一个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 pitfall I 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);
                        }
                    });
                });
            });
        });
    });
});

#7


1  

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;

推荐阅读
author-avatar
宛雨万青惠雯
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有