web-dev-qa-db-ja.com

関数が特定のメソッド/関数を呼び出すかどうかをテストするにはどうすればよいですか?

関数が特定のメソッドまたは外部関数を呼び出すかどうかをテストする方法はモカにありますか?

MochaをChaiで使用していますが、他のアサーションライブラリを使用できます。


OK、メトイドが呼び出されているかどうかのテストは、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をテストする方法はわかりましたが、2番目と3番目のテストを実装する方法が見つかりません。

17
Hal Carleton

したがって、この質問は実際には2つの質問でした。

最初に、「メソッドが呼び出されているかどうかをテストする方法」:この例のコードをレイアウトしましたが、基本的に、sinon.jsを使用して、メソッドを「スパイ」にラップするだけです。そのスパイが呼び出されたことを期待するテストを書くことができます。

2番目に、「プライベート関数(モジュールの一部としてエクスポートされなかったもの)が呼び出されたかどうかをテストする方法」:

基本的にはありません。本番環境ではなくテスト環境でこれらの関数をエクスポートすることは可能ですが、これは私には少々ハックしすぎるようです。

別のモジュールを呼び出すときは、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');

  });

};
5
Hal Carleton

expectアサーションライブラリをMochaと一緒に使用していますが、Chaiにも類似のメソッドがある場合があります


最初

Spiesを使用して、関数が特定のメソッド/関数を呼び出すかどうかをテストできます。上記のコードでこれを行いました。

二番目

テストしているコードの問題はコンテキストです。したがって、私はこの答えでそれを扱います。外部関数が呼び出されたかどうかをテストできますが、外部関数にはコンテキストが必要なので、コードを変更する必要があります。

例としてbar(モジュール)を使用しています。 xyz(関数)の場合は、2番目の方法に進みます。説明はどちらも同じです。

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

このスニペットでは、barthis.barを使用してFoo.prototypeを後で設定する必要があります。では、どうすれば同じ名前の2つの変数を設定し、お互いをうまく参照できますか?

答えはコンテキストとスコープです。 this.barは、barコンテキストセット(thisを指す)であるFoo変数を参照しています。一方、bar-thisがないことに注意してください-関数の(モジュール)スコープで設定されたbar変数を参照しています。

それで、あなたはあなたのFoo.prototype.barをテストするかもしれません、それはモジュールメソッドであり、コンテキストを持っていて、あなたはそれをスパイするかもしれないからです。必要なbarはスコープ付きであるため、スパイすることはできません(プライベートと考えてください)。

よく読んでください: http://ryanmorr.com/understanding-scope-and-context-in-javascript/

3
jpenna