如何测试函数是否调用特定方法/函数?

Hal*_*ton 16 javascript tdd mocha.js node.js chai

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

我正在使用Mocha和Chai,但我对任何其他断言库都开放.


好的,所以使用sinon测试是否正在调用methid是非常容易的.我不确定测试是否正在调用外部函数.所以我更新了这些例子来代表一些更"现实世界"的东西.我工作的一个节点的应用程序,所以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 () {};
Run Code Online (Sandbox Code Playgroud)

bar.js

var bar = module.exports = function () {};
Run Code Online (Sandbox Code Playgroud)

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 () {
    // ????????????
  });
});
Run Code Online (Sandbox Code Playgroud)

所以你可以看到我已经找到了如何测试Foo.prototype.bar,但我找不到实现第二和第三次测试的方法.

Hal*_*ton 5

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

首先,“如何测试方法是否被调用”:我在示例中列出了代码,但基本上,使用sinon.js只需将方法包装在“间谍”中即可,使您可以编写测试希望间谍已经被召唤。

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

基本上,您不需要。可以在测试环境中而不是在生产环境中导出这些功能,但这对我来说似乎太过分了。

我得出的结论是,在调用另一个模块时,您应该只是中断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'
  }
};
Run Code Online (Sandbox Code Playgroud)

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

  });

};
Run Code Online (Sandbox Code Playgroud)

  • 如果它还包含一个间谍示例,这将是一个很好的答案。 (2认同)

jpe*_*nna 5

我正在使用expect断言库Mocha,但Chai可能有类似的方法


第一的

您可以使用 Spies 测试函数是否调用特定方法/函数。你在上面的代码中做到了这一点。

第二

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

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

1.bar在对象内部导出

bar.js

var bar = module.exports = { 
  bar: function () {}; 
}
Run Code Online (Sandbox Code Playgroud)

foo.js

var Foo = module.exports = function () {
  bar.bar();
  ....
};
Run Code Online (Sandbox Code Playgroud)

通过这种方式,您可以监视它:

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();
Run Code Online (Sandbox Code Playgroud)

2.设置barmodule为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
Run Code Online (Sandbox Code Playgroud)

fooSpec.js

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

  new Foo();

  expect(barSpy).toHaveBeenCalled();    
});
Run Code Online (Sandbox Code Playgroud)

解释

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

要说清楚:

var bar = require('bar');

var Foo = module.exports = function () {
  this.bar();
  bar();
};
Foo.prototype.bar = function () {};
Run Code Online (Sandbox Code Playgroud)

在这个片段中,你需要bar和以后的设置this.bar使用Foo.prototype。那么,如何设置两个同名的变量并很好地相互引用呢?

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

所以,你可以测试你的Foo.prototype.bar,因为它是一个模块方法,有一个上下文,你可以监视它。购买您无法监视所需的内容,bar因为它是有范围的(将其视为私有的)。

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

  • toHaveBeenCalled 是一个玩笑方法,因此在摩卡中无效 (3认同)