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.
var bar = require('bar');
var xyz = function () {};
var Foo = module.exports = function () {
this.bar();
bar();
xyz();
};
Foo.prototype.bar = function () {};
var bar = module.exports = function () {};
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.
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:
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'
}
};
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');
});
};
J'utilise expect
bibliothèque d'assertions avec Mocha
, mais Chai
peut avoir des méthodes analogues
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.
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. Pourxyz
(fonction), passez à la deuxième méthode. L'explication est la même pour les deux.
bar
à l'intérieur d'un objetvar bar = module.exports = {
bar: function () {};
}
var Foo = module.exports = function () {
bar.bar();
....
};
De cette façon, vous pouvez l'espionner en faisant:
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();
bar
comme méthode prototype de FooSi 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.
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
it('should call the module bar immediately', function () {
var barSpy = expect.spyOn(Foo.prototype, 'barModule');
new Foo();
expect(barSpy).toHaveBeenCalled();
});
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/