【问题标题】:How do I test if a function calls a specific method/function?如何测试函数是否调用特定的方法/函数?
【发布时间】:2014-12-29 13:04:21
【问题描述】:

Mocha 中有没有办法测试一个函数是否调用了特定的方法或外部函数?

我正在使用 Mocha 和 Chai,但我对任何其他断言库持开放态度。


好的,因此使用 sinon 测试是否正在调用方法非常容易。我不确定是否要测试是否正在调用外部函数。所以我更新了这些例子来代表一些更“真实世界”的东西。我正在开发一个节点应用程序,所以foo.jsbar.js 都是模块。

示例:

foo.js

var bar = require('bar');
var xyz = function () {};

var Foo = module.exports = function () {
  this.bar();
  bar();
  xyz();
};
Foo.prototype.bar = function () {};

bar.js

var bar = module.exports = function () {};

fooSpec.js

var chai      = require('chai');
var sinon     = require('sinon');
var sinonChai = require('sinonChai');
var expect    = chai.expect;
var Foo       = require('../lib/foo');

chai.use('sinonChai');

describe('Foo', function () {

  var method;

  beforeEach(function (done) {
    method = sinon.spy(Foo.prototype, 'bar');
    done();
  });
  afterEach(function (done) {
    method.restore();
    done();
  });

  it('should call Foo.prototype.bar() immediately', function () {

    new Foo();
    expect(method).to.have.been.called;
    
  });

  it('should call the module bar immediately', function () {
    // ????????????
  });

  it('should call xyz() immediately', function () {
    // ????????????
  });
});

如您所见,我已经知道如何测试Foo.prototype.bar,但我找不到实现第二个和第三个测试的方法。

【问题讨论】:

  • 如果您的 sn-ps 旨在成为模块,那么说明您从模块中导出的含义会很有帮助,因为这对您将获得的答案类型很重要。
  • 我更详细地更新了示例。示例文件是节点应用程序中的模块。

标签: javascript node.js tdd mocha.js chai


【解决方案1】:

所以这个问题真的是二合一的。

首先,“如何测试一个方法是否被调用”: 我在示例中为此列出了代码,但基本上,使用 sinon.js,您只需将方法包装在“spy”中,这样​​您就可以编写一个期望该 spy 已被调用的测试。

其次,“如何测试是否调用了私有函数(未作为模块的一部分导出的函数)”:

基本上,你不会。在测试环境中而不是在生产环境中可以导出这些函数,但这对我来说似乎有点太老套了。

我得出的结论是,在调用另一个模块时,您应该打破 TDD 循环,而不是对此进行测试,因为它可能只是少量代码,并且该模块已经被自己测试过。

如果您正在调用在您的模块中声明的私有函数并且想要对其进行测试,您应该编写一个更广泛的测试来测试该函数被调用的结果,而不是测试该函数是否被调用或函数中实际发生了什么。

这是一个非常简单的例子:

foo.js

var _ = require('lodash');

var Foo = module.exports = function (config) {

  this.config = _.merge({
      role: 'user',
      x: '123',
      y: '321'
    },
    config);

  this.config.role = validateRole(this.config.role);
};

var validateRole = function (role) {
  var roles = [
    'user', 'editor', 'admin'
  ];

  if (_.contains(roles, role)) {
    return role;
  } else {
    return 'user'
  }
};

fooSpec.js

var chai = require('chai');
var expect = chai.expect;
var Foo = require('../lib/foo');

describe('Foo', function () {

  it('should set role to \'user\' if role is not valid', function () {

    var foo = new Foo({role: 'invalid'});
    expect(foo.config.role).to.equal('user');

  });

};

【讨论】:

  • 这很有帮助,因为我一直在努力通过查看在我的组件初始化时是否调用了某些函数并想知道这是否真的有用,我是否正在正确编写测试。但从您所说的来看,实际上最好测试调用该函数的 result 是否发生,因为简单地验证调用了某个名称的东西实际上并不能增加人们对事情正常工作的信心他们应该。
  • 如果它还包含一个间谍示例,这将是一个很好的答案。
【解决方案2】:

我将expect 断言库与Mocha 一起使用,但Chai 可能有类似的方法


首先

您可以使用 Spies 测试函数是否调用特定的方法/函数。您在上面的代码中执行了此操作。

第二

您正在测试的代码的问题是上下文。所以我将在这个答案中解决它。您可以测试是否调用了外部函数,但它需要上下文,因此您可能需要更改代码。

我以bar(模块)为例。对于xyz(函数),请转到第二种方法。两者的解释是一样的。

1。在对象内导出bar

bar.js

var bar = module.exports = { 
  bar: function () {}; 
}

foo.js

var Foo = module.exports = function () {
  bar.bar();
  ....
};

这样你就可以窥探它的行为:

fooSpec.js

it('should call the module bar immediately', function () {

  //note I'm getting the bar method from the exported object (bar module)
  var bar = expect.spyOn(bar, 'bar'); 

  new Foo();

  expect(bar).toHaveBeenCalled();

2。设置bar模块为Foo的原型方法

如果你不想改变bar.js,你可以将需要的模块设置为Foo的原型方法。然后你就有了可以窥探的上下文。

foo.js

var bar = require('./bar');

var Foo = module.exports = function () {
  this.bar();
  this.barModule();
};
Foo.prototype.bar = function () {};
Foo.prototype.barModule = bar; // setting here as barModule

fooSpec.js

it('should call the module bar immediately', function () {
  var barSpy = expect.spyOn(Foo.prototype, 'barModule');

  new Foo();

  expect(barSpy).toHaveBeenCalled();    
});

说明

您必须做的更改是更改变量的上下文。

说清楚:

var bar = require('bar');

var Foo = module.exports = function () {
  this.bar();
  bar();
};
Foo.prototype.bar = function () {};

在这个 sn-p 中,您需要 bar,然后使用 Foo.prototype 设置 this.bar。那么,如何设置 2 个具有相同名称的变量并很好地相互引用呢?

答案是上下文和范围。您的this.bar 引用了this 上下文中设置的bar 变量(指向Foo)。另一方面,您的 bar - 注意没有 this - 引用了在函数(模块)范围内设置的 bar 变量。

所以,你可以测试你的Foo.prototype.bar,因为它是一个模块方法,有一个上下文,你可以监视它。购买你不能监视所需的bar,因为它是作用域的(认为它是私有的)。

好读:http://ryanmorr.com/understanding-scope-and-context-in-javascript/

【讨论】:

  • toHaveBeenCalled 是个玩笑的方法,所以在 mocha 中无效
猜你喜欢
  • 2017-12-27
  • 1970-01-01
  • 2018-03-17
  • 1970-01-01
  • 2014-02-14
  • 1970-01-01
  • 2020-08-21
  • 1970-01-01
  • 2016-08-07
相关资源
最近更新 更多