我怎么能嘲笑为RequireJS单元测试的依赖?(How can I mock dependenci

2019-06-14 20:18发布

我有一个AMD模块,我想测试,但我想模拟出的不是加载实际依赖它的依赖。 我使用requirejs,为我的模块的代码看起来是这样的:

define(['hurp', 'durp'], function(Hurp, Durp) {
  return {
    foo: function () {
      console.log(Hurp.beans)
    },
    bar: function () {
      console.log(Durp.beans)
    }
  }
}

我怎样才能模拟出hurpdurp这样我就可以有效的单元测试?

Answer 1:

所以看完这篇文章我想出了使用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/'
  });
}

因此,它会创建一个新的上下文,其中用于定义HurpDurp将由您传递给函数的对象设置。 该名的Math.random也许是有点脏,但它的工作原理。 因为如果你有一大堆的测试,你需要创建新的上下文,每间套房,以防止重用你的嘲笑,或当你想真正的requirejs模块加载嘲笑。

在你的情况是这样的:

(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');
      })
    });
  });
})();

所以我使用了一段时间,其真正强大的生产中这种做法。



Answer 2:

你可能想看看新的Squire.js LIB

从文档:

Squire.js是依赖注入的Require.js用户做出嘲讽的依赖容易!



Answer 3:

我发现这个问题,有三种不同的解决方案,他们都不愉快。

定义依赖在线

define('hurp', [], function () {
  return {
    beans: 'Beans'
  };
});

define('durp', [], function () {
  return {
    beans: 'durp beans'
  };
});

require('hurpdhurp', function () {
  // test hurpdurp in here
});

的fugly。 你必须弄乱你的测试有很多AMD的样板。

加载模拟依赖从不同路径

这涉及使用单独的config.js文件来定义每个指向嘲笑而不是原来的依赖关系的依赖关系的路径。 这也是丑陋的,需要吨的测试文件和配置文件的创建。

假货在节点

这是我目前的解决方案,但仍然是一个可怕的一个。

您可以创建自己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);

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

  1. 运行节点的测试,所以用浏览器自动化无需复杂。
  2. 不太需要在测试中凌乱AMD样板。
  3. 你能使用eval愤怒,并想象克罗克福德愤怒爆炸。

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

  1. 既然你是在节点测试,你不能做与浏览器事件或DOM操作任何东西。 不仅有利于测试逻辑。
  2. 还是有点笨重成立。 你需要模拟出define在每个测试,因为这是你的实际测试运行。

我工作的一个测试运行,给出这种东西用优雅的语法,但我仍然有问题1没有很好的解决方案。

结论

在requirejs惩戒DEPS吸硬。 我发现了一个方式,sortof的作品,但我仍然不是很满意。 请让我知道,如果你有更好的想法。



Answer 4:

有一个config.map选项http://requirejs.org/docs/api.html#config-map 。

关于如何对使用它:

  1. 定义正常模块;
  2. 定义桩模块;
  3. 配置RequireJS expicitely;

     requirejs.config({ map: { 'source/js': { 'foo': 'normalModule' }, 'source/test': { 'foo': 'stubModule' } } }); 

在这种情况下,正常和测试代码,你可以使用foo模块,这将是真正的模块引用和相应的存根。



Answer 5:

您可以使用testr.js嘲笑的依赖。 您可以设置testr加载模拟的依赖,而不是原来的。 下面是一个例子用法:

var fakeDep = function(){
    this.getText = function(){
        return 'Fake Dependancy';
    };
};

var Module1 = testr('module1', {
    'dependancies/dependancy1':fakeDep
});

看看这个还有: http://cyberasylum.janithw.com/mocking-requirejs-dependencies-for-unit-testing/



Answer 6:

这个答案是基于安德烈亚斯Köberle的答案 。
这是不是很简单,我实现和理解他的解决方案,所以它是如何工作的,我会更详细一点解释,以及一些需要避免的陷阱,希望这将有助于未来的游客。

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

使用类似的乡绅 ,我没有工作,出于某种原因,当我用它,测试框架扔错误:

类型错误:无法读取属性“叫”未定义

RequireJs有可能映射模块ID到其他模块ID。 它还允许创建一个require的功能 ,它使用一个不同的配置超过了全球require
这些功能是至关重要的这个解决方案的工作。

这里是我的版本的模拟代码,包括(很多)评论(我希望它可以理解的)。 我把它包在模块内,这样的测试可以很容易地需要它。

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;
});

我遇到的最大缺陷在于 ,它的字面花了我小时,创造了RequireJs配置。 我试过(深)复制,并且只覆盖必要的属性(如上下文或地图)。 这不起作用! 只复制baseUrl ,这工作正常。

用法

要使用它,需要它在你的测试中,创造了嘲笑,然后把它传递给createMockRequire 。 例如:

var ModuleMock = function () {
    this.method = function () {
        methodCalled += 1;
    };
};
var mocks = {
    "ModuleIdOrPath": ModuleMock
}
var requireMocks = mocker.createMockRequire(mocks);

在这里, 一个完整的测试文件的例子

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);
                        }
                    });
                });
            });
        });
    });
});


Answer 7:

如果你想使该隔离一个单元有普通的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;


文章来源: How can I mock dependencies for unit testing in RequireJS?