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

IT技术 javascript unit-testing mocking requirejs
2021-03-03 22:40:50

我有一个要测试的 AMD module,但我想模拟它的依赖项而不是加载实际的依赖项。我正在使用 requirejs,我的module的代码如下所示:

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

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

6个回答

所以在阅读这篇文章后,我想出了一个解决方案,它使用 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 module时加载模拟。

在您的情况下,它看起来像这样:

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

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

我在 Rails 中使用它(使用 jasminerice/phantomjs),它是我找到的使用 RequireJS 进行模拟的最佳解决方案。
2021-04-20 22:40:50
更新:对于考虑此解决方案的任何人,我建议查看下面提到的 squire.js ( github.com/iammerrick/Squire.js )。这是一个很好的解决方案实现,类似于这个解决方案,在需要存根的地方创建新的上下文。
2021-04-26 22:40:50
它只会模拟您传递给createContext函数的依赖项因此,在您的情况下,如果您只传递{hurp: 'hurp'}给函数,则该durp文件将作为正常依赖项加载。
2021-05-05 22:40:50
我喜欢你在这里所做的......特别是因为你可以为每个测试加载不同的上下文。我唯一希望我可以改变的是,它似乎只有在我模拟所有依赖项时才有效。如果模拟对象在那里,您是否知道返回模拟对象的方法,但如果未提供模拟,则退回到从实际 .js 文件中检索?我一直在尝试挖掘 require 代码来弄清楚,但我有点迷失了。
2021-05-10 22:40:50
+1 不漂亮,但在所有可能的解决方案中,这似乎是最不丑陋/混乱的解决方案。这个问题值得更多关注。
2021-05-12 22:40:50

您可能想查看新的Squire.js 库

从文档:

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

我在影响其他测试的 squire 副作用方面遇到了很多问题,不能推荐它。我会推荐npmjs.com/package/requirejs-mock
2021-05-03 22:40:50
强烈推荐!我正在更新我的代码以使用 squire.js,到目前为止我非常喜欢它。非常非常简单的代码,引擎盖下没有什么大魔法,但以(相对)容易理解的方式完成。
2021-05-09 22:40:50

我找到了解决这个问题的三种不同的解决方案,但没有一个是令人愉快的。

内联定义依赖项

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

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

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

闷闷不乐。你必须用大量的 AMD 样板来打乱你的测试。

从不同路径加载模拟依赖

这涉及使用单独的 config.js 文件为每个指向模拟而不是原始依赖项的依赖项定义路径。这也很丑陋,需要创建大量的测试文件和配置文件。

在节点中伪造它

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

您创建自己的define函数来为module提供自己的模拟并将您的测试放在回调中。然后你eval的module来运行你的测试,像这样:

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. 在 node 中运行你的测试,所以不会搞乱浏览器自动化。
  2. 在您的测试中更少需要凌乱的 AMD 样板。
  3. 你可以eval在愤怒中使用,并想象克罗克福德怒火中烧。

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

  1. 由于您在 node 中进行测试,因此您无法对浏览器事件或 DOM 操作进行任何操作。只适合测试逻辑。
  2. 设置起来还是有点笨拙。您需要define在每个测试中模拟,因为这是您的测试实际运行的地方。

我正在开发一个测试运行器,为这类东西提供更好的语法,但我仍然没有解决问题 1 的好方法。

结论

在 requirejs 中模拟 deps 很难。我找到了一种有效的方法,但仍然不太满意。如果您有更好的想法,请告诉我。

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

关于如何使用它:

  1. 定义普通module;
  2. 定义存根module;
  3. 显式配置RequireJS;

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

在这种情况下,对于普通代码和测试代码,您可以使用foo将成为真正module引用和存根的module。

这种方法对我来说非常有效。就我而言,我将此添加到测试运行程序页面的 html -> map: { '*': { 'Common/Modules/usefulModule': '/Tests/Specs/Common/usefulModuleMock.js' } }
2021-05-16 22:40:50

您可以使用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/

我真的很想让 testr.js 工作,但感觉还不能胜任这项任务。最后,我将使用@Andreas Köberle 的解决方案,它将为我的测试添加嵌套上下文(不漂亮),但始终有效。我希望有人可以专注于以更优雅的方式解决这个解决方案。我会继续关注 testr.js,如果/当它工作时,将进行切换。
2021-04-18 22:40:50
@shioyama 嗨,感谢您的反馈!我很想看看您是如何在测试堆栈中配置 testr.js 的。很高兴帮助您解决您可能遇到的任何问题!如果你想在那里记录一些东西,还有 github 问题页面。谢谢,
2021-04-22 22:40:50
@MattyF 抱歉,我现在什至不记得 testr.js 对我不起作用的确切原因是什么,但我得出的结论是,使用额外的上下文实际上很好,而且实际上是一致的以及 require.js 是如何用于模拟/存根的。
2021-05-08 22:40:50