Sinon: Spy `returnValue` ne fonctionne pas avec les générateurs

Créé le 3 janv. 2015  ·  20Commentaires  ·  Source: sinonjs/sinon

Lorsque vous travaillez avec des fonctions de générateur, la valeur de retour d'un espion est toujours undefined . Voici un cas de test qui a échoué :

require('should');

var sinon = require('sinon');
var co = require('co');

var foo = {
  bar: function() {
    return 'bar';
  },
  biz: function *() {
    return 'biz';
  }
}

describe('Return value', function() {
  it('should work with regular functions', function() {
    sinon.spy(foo, 'bar');

    foo.bar();

    foo.bar.firstCall.returnValue.should.equal('bar');
  });

  it('should work with generator functions', co.wrap(function*() {
    sinon.spy(foo, 'biz');

    var result = yield foo.biz();

    result.should.equal('biz');
    foo.biz.firstCall.returnValue.should.equal('biz');
  }));
});

Exécutez avec npm install co mocha should sinon && ./node_modules/.bin/mocha --harmony index.js .

2.x Unverified

Tous les 20 commentaires

Sinon ne prend pas encore en charge les fonctionnalités ES6. Le travail n'a pas encore commencé, donc je ne sais pas quand cela fonctionnera.

@mantoni , d'accord, c'était plus une demande de fonctionnalité :) voulez-vous la laisser ouverte, par exemple, avec une sorte de balise ("es6" ?) De sorte que lorsque cela est ajouté à la feuille de route, il est plus facile de re -évaluer ce qui doit être fait?

Ce serait une excellente fonctionnalité à avoir.

@ruimarinho j'ai trouvé ceci https://github.com/ingameio/SinonES6.JS
le remplacement de require("sinon") par require("sinon-es6") semble faire passer vos tests

--
édité par @ fatso83 le 22 juin 2017 :
Cela ne semble pas être vrai. J'ai testé cela manuellement. Il échoue tout de même, ce qui est tout à fait logique car sinon-es6 _implémente uniquement son support pour les générateurs de moquerie _.

Est-ce que quelqu'un travaille là-dessus?

@emgeee pas que je sache.

+1 @ruimarinho sinon.js avec les fonctionnalités es6 serait génial d'avoir !

@ingameio veut -il faire un PR avec vos modifications ?

@ fatso83 merci pour la suggestion !
Je ferai le PR bientôt ;)

J'ai des problèmes lors de l'exécution de buster-test avec la version packagée. J'ai fait quelques débogages mais je manque de temps ces jours-ci donc je n'ai pas pu résoudre le problème.
Si quelqu'un veut m'aider, je pourrais apporter les modifications à mon fork, dites-le moi et je le ferai.

@gaguirre C'est une excellente nouvelle. Maximillian vient de convertir tous les tests en Mocha et a apporté quelques modifications à la configuration des tests pendant ce temps-là, alors peut-être que les choses fonctionneront si vous récupérez les dernières modifications de master ?

@gaguirre Au cas où quelqu'un tomberait sur ce fil, je pense que ce pourrait être une idée de pousser les modifications à votre fork de toute façon afin qu'un passant puisse y jeter un coup d'œil.

@ fatso83 J'ai poussé une première version à underscope/sinon .
Le problème principal se produit lors de l'exécution des tests avec un moteur qui ne prend pas en charge es6 (phantomjs dans ce cas) : il échoue lors de l'analyse du code, j'utilise donc eval() comme solution de contournement car il peut être entouré d'un try/ prise. Je pense que ce n'est pas acceptable mais c'est un point de départ.

En ce moment, j'essaie d'envelopper l'appel du générateur dans un autre fichier et de l'exiger dynamiquement, mais cela ne fonctionne pas, je suppose que parce que browserify regroupe même les fichiers dynamiquement requis. Je me demande si certains fichiers pourraient être exclus lors de l'exécution npm run test-headless .

Selon vous, quelle pourrait être la meilleure solution ?

Merci pour les commentaires, @gaguirre . Donc, le problème fondamental est que nous avons besoin d'un moyen d'éviter l'analyse si le moteur ne le prend pas en charge. C'est un peu plus difficile que la simple détection de fonctionnalités, comme nous le faisons avec WebWorkers, etc.

Déléguer simplement la vérification réelle est aussi simple que if(require('generator-support')){ ... } , mais cela ne résout pas le problème d'analyse réel.

Des idées sur la façon de gérer cela proprement, @mantoni et @mroderick ?

PS Je suis absent en vacances hors ligne dans quelques heures, je ne pourrai donc pas lire les réponses avant quelque temps après Pâques.

Je ne peux pas dire si ce niveau de remplacement du générateur fonctionne réellement (en particulier dans les scénarios orientés coroutine) - il semble qu'une certaine émulation du comportement asynchrone pourrait être de mise. Mais je pense qu'il y a un moyen de contourner le problème d'analyse.

Suggestion :

Extrayez le code du générateur dans un fichier séparé et require() ce fichier uniquement à la demande lors de l'encapsulation d'une fonction de générateur, c'est-à-dire :

?/es6-support.js :

"use strict";

exports.getGeneratorWrapper = function(method, ctx, args) {
    return function* () {
        return mockObject.invokeMethod(method, ctx, args);
    }
}

lib/sinon/mock.js->attend... :

var wrapper = function () {
    return mockObject.invokeMethod(method, this, arguments);
});

if (/^function\s*\*/.test(method.toString())) {
    wrapper = require('es6-support').getGeneratorWrapper(method, this, arguments);
}

wrapMethod(this.object, method, wrapper);

(Faites également de même avec les tests unitaires et assurez-vous que le suivi de la couverture du code n'entraîne pas l'inclusion automatique du fichier 'es6-support'.)

Suggestion alternative :

La compatibilité ES5 sera-t-elle toujours un objectif valable au moment où sinon la version 2.0 sera prête à être publiée ? Il est peut-être temps de dire aux quelques personnes qui prennent encore en charge les environnements ES5 hérités uniquement sans l'utilisateur de transpileurs qu'ils seront laissés pour compte sur 1.x.

@evan-king Cette exigence conditionnelle n'est pas une solution, car elle se cassera dans les versions de notre navigateur. Comme les exigences conditionnelles annulent l'analyse statique du graphe de dépendances, des outils tels que WebPack, Rollup et Browserify ne pourront pas y faire face. C'est soit totalement à l'intérieur, soit totalement à l'extérieur.

Et oui, la compatibilité ES5 est une chose. La prise en charge du générateur ES6 est au mieux médiocre, et forcer les gens à utiliser des outils supplémentaires pour utiliser Sinon pour leurs projets relèvera la barre des tests. Et pour beaucoup, ce seuil est suffisamment élevé tel quel. Être capable d'inclure simplement une balise de script pour le faire fonctionner est une fonctionnalité importante à mon humble avis. Nous pourrions casser la compatibilité ES5 à un moment donné, mais ce n'est pas sur notre feuille de route et n'a même pas été discuté pour Sinon 3. Sinon 2 est en effet sorti depuis un certain temps ; il y a juste quelques nuisances mineures qui nous empêchent de faire une sortie officielle.

Il existe essentiellement deux façons d'obtenir la compatibilité ES6 sans casser la compatibilité d'exécution ES5 existante que je peux proposer, et je ne suis pas vraiment sûr de la dernière (je ne l'ai pas testée):

Évaluation conditionnelle des modules

C'est un hack, mais c'est assez simple. Cela signifie que nous pouvons nous fier à l'inlining des actifs statiques et aux tests de fonctionnalités ES6 pour décider d'évaluer ou non le module requis. Cela garantira la compatibilité ES5 (Sinon ne sera corrigé que si le runtime prend en charge la syntaxe), il n'y a pas besoin d'outils supplémentaires sous la forme de Babel ni du côté du fabricant de bibliothèques ni de l'utilisateur client, et tester ES6 cassera les navigateurs ES5 qui aurait échoué de toute façon.

En supposant que quelque chose comme brfs soit en place, le code ressemblerait à ceci :

_runtime-features.js_

var features = {};
try { 
    new Function("var foo = function* foo(){ }") ;
    features.generatorSupport = true; 
} 
catch(err){ features.generatorSupport = false; }
module.exports = features;

_es6-generator-wrapper.js_

return function* () {
    return mockObject.invokeMethod(method, ctx, args);
}

_es6.js_

var features = require('./runtime-features');

if( features.generatorSupport ) {
    var code = fs.readFileSync('./es6-generator-wrapper.js');
    module.exports.getGeneratorWrapper = new Function("mockObject", "method", "ctx", "args", code);
} else {
    module.exports.getGeneratorWrapper = function() { throw new TypeError("You tried using a generator function in a runtime only capable of running ES5 code"); }
}

Babelifier Sinon

C'est celui dont je ne suis pas sûr car je n'ai pas encore essayé. Si nous transpilons la construction via Babel vers ES5, nous pouvons écrire du code ES6 partout, éviter de sauter à travers des cerceaux comme je l'ai fait ci-dessus, et nous pouvons toujours utiliser les mêmes types de vérifications pour les générateurs. Ils seront simplement implémentés à l'aide de constructions ES5. Bien sûr, tester ES6 dans les navigateurs ES5 échouera toujours. Cela a le même avantage que le précédent côté client, mais nous pourrions entraver les contributions car les connaissances ES2015 sur des choses telles que yield , async , function*() sont loin d'être atteintes un large public.

+1

@rpavlovs , il est vraiment inutile d'ajouter +1 au fil. L'interface utilisateur GitHub a un bouton "ajouter une réaction" en haut de chaque commentaire si vous avez besoin d'exprimer vos émotions. Un +1 ne fera rien. Une demande d'extraction qui implémente l'une des suggestions ci-dessus (ou quelque chose de plus intelligent), en revanche, a de bien meilleures chances de résoudre ce problème 😄

J'aimerais savoir à quoi ressemblerait la prise en charge de l'API pour les générateurs , car après avoir utilisé quelques heures à ce sujet, je ne suis toujours pas vraiment sûr de ce que les gens aimeraient voir.

Pour commencer à étoffer cela, j'ai créé une nouvelle branche qui contient les modifications de @ingameio à l'API fictive, tout en ne brisant pas la compatibilité ES5 (en utilisant le hack mentionné ci-dessus).

Ce qui m'énerve un peu, c'est que je ne peux vraiment pas dire comment tester les modifications originales d'Ingameio, car aucun des exemples de test ne fonctionne - même l'exemple du fork n'est pas complet, et je n'arrive pas à casser les choses avant/après changements.

Les générateurs sont des choses simples : de doux êtres synchrones qui se souviennent de leur passé. Donc, s'il vous plaît, ne résolvez aucun exemple avec co et d'autres choses qui ne sont pas liées, car il est plus difficile de voir ce qui est voulu/ne fonctionne pas. L'exemple du haut, par exemple, est assez compliqué, et il semble également confondre ce que fait yield , car il s'attend à ce que la valeur de retour de "l'expression de rendement" soit la même que la "valeur produite". Le result du rendement est la valeur passée dans le next() du générateur ( MDN )

Je réalise que les exemples utilisant co ne l'utilisent probablement que pour pouvoir utiliser yield directement dans le test Mocha, mais enveloppez simplement votre exemple dans un IIFE ou une autre manière d'obtenir la même chose pour pour plus de clarté.

Ceci est un test simple de la prise en charge des générateurs (fonctionne dans le Sinon d'aujourd'hui):

require("should");

var sinon = require("../sinon");

var foo = {
    bar: function () {
        return "bar";
    },
    biz: function *() {
        return "biz";
    }
};

describe("generator support", function () {
    it('should work with generator functions',  function(){
        var spy = sinon.spy(foo, 'biz');

        var iterator = foo.biz();
        var result = iterator.next();

        result.value.should.equal('biz');
        result.done.should.equal(true);
        spy.firstCall.returnValue.should.be.an.Object();
        spy.firstCall.returnValue.next.should.be.a.Function();
    });
});

Maintenant, quelles extensions d'API aimerions-nous ?
D'après le test original, je suppose que nous aimerions voir quelque chose comme

foo.biz.firstGeneratedValue.should.equal('biz');
ou
foo.biz.generatedValue[0].should.equal('biz');
?

cc @ruimarinho

Je ferme ce problème, car le test d'origine comportait une erreur et je ne vois aucun problème avec la gestion du générateur dans Sinon.

Veuillez vous joindre à la discussion sur l'apparence d'une API pour gérer les générateurs (et ses itérateurs associés) dans le numéro 1467.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

zimtsui picture zimtsui  ·  3Commentaires

NathanHazout picture NathanHazout  ·  3Commentaires

OscarF picture OscarF  ·  4Commentaires

fearphage picture fearphage  ·  3Commentaires

andys8 picture andys8  ·  4Commentaires