web-dev-qa-db-fra.com

Comment tester si une fonction appelle une méthode / fonction spécifique?

Existe-t-il un moyen dans Mocha de tester si une fonction appelle une méthode spécifique ou une fonction externe?

J'utilise Mocha avec Chai, mais je suis ouvert à toute autre bibliothèque d'assertions.


Ok, donc tester si un méthid est appelé est assez facile à utiliser sinon. Je ne suis pas sûr de tester pour voir si une fonction externe est appelée. J'ai donc mis à jour les exemples pour représenter quelque chose d'un peu plus "monde réel". Je travaille sur une application de nœud, donc foo.js et bar.js sont les deux modules.

Exemple:

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

Comme vous pouvez le voir, j'ai compris comment tester pour Foo.prototype.bar, mais je ne trouve pas de moyen d'implémenter les deuxième et troisième tests.

17
Hal Carleton

Donc, cette question était vraiment deux en un.

Premièrement, "comment tester si une méthode est appelée": j'ai présenté le code pour cela dans l'exemple, mais en gros, en utilisant sinon.js vous enveloppez simplement la méthode dans un "espion" qui vous permet d'écrire un test qui attend que cet espion ait été appelé.

Deuxièmement, "comment tester si une fonction privée (qui n'a pas été exportée dans le cadre du module) a été appelée":

Fondamentalement, vous ne le faites pas. Il est possible d'exporter ces fonctions dans un environnement de test et non en production, mais cela me semble un peu trop hacky.

Je suis arrivé à la conclusion que lorsque vous appelez un autre module, vous devez simplement interrompre le cycle TDD et ne pas le tester car cela va probablement être une petite quantité de code et le module aura déjà été testé seul.

Si vous appelez une fonction privée qui est déclarée dans votre module et que vous voulez la tester, vous devez écrire un test plus large qui teste le résultat de cette fonction au lieu de tester si la fonction est appelée ou ce qui est réellement qui se passe dans la fonction.

Voici un exemple très simple:

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

J'utilise expect bibliothèque d'assertions avec Mocha, mais Chai peut avoir des méthodes analogues


Première

Vous pouvez tester si une fonction appelle une méthode/fonction spécifique à l'aide d'espions. Vous avez fait cela dans votre code ci-dessus.

Seconde

Le problème avec le code que vous testez est le contexte. Je vais donc en parler dans cette réponse. Vous pouvez tester si une fonction externe est appelée, mais elle a besoin d'un contexte , vous devrez donc peut-être changer votre code.

J'utilise bar (module) comme exemple. Pour xyz (fonction), passez à la deuxième méthode. L'explication est la même pour les deux.

1. Exporter bar à l'intérieur d'un objet

bar.js

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

foo.js

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

De cette façon, vous pouvez l'espionner en faisant:

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. Définissez le module bar comme méthode prototype de Foo

Si vous ne voulez pas changer bar.js, vous pouvez définir le module requis comme méthode prototype de Foo. Ensuite, vous avez un contexte à espionner.

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

Explication

Les modifications que vous devez effectuer sont destinées à modifier le contexte de vos variables.

Pour être clair:

var bar = require('bar');

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

Dans cet extrait, vous avez besoin de bar et d'une configuration ultérieure this.bar en utilisant Foo.prototype. Alors, comment pouvez-vous définir 2 variables avec le même nom et se référencer bien?

La réponse est le contexte et la portée. Votre this.bar fait référence à la variable bar définie dans le contexte this (qui pointe vers Foo). D'un autre côté, votre bar - notez qu'il n'y a pas de this - fait référence à la variable bar définie dans la portée (module) de la fonction.

Vous pouvez donc tester votre Foo.prototype.bar, puisqu'il s'agit d'une méthode de module, a un contexte et vous pouvez l'espionner. Achetez, vous ne pouvez pas espionner le bar requis car il est limité (pensez-y comme privé).

Bonne lecture: http://ryanmorr.com/understanding-scope-and-context-in-javascript/

3
jpenna