Sinon: Spy `returnValue` no funciona con generadores

Creado en 3 ene. 2015  ·  20Comentarios  ·  Fuente: sinonjs/sinon

Cuando se trabaja con funciones de generador, el valor de retorno de un espía siempre es undefined . Aquí hay un caso de prueba fallido:

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

Corre con npm install co mocha should sinon && ./node_modules/.bin/mocha --harmony index.js .

2.x Unverified

Todos 20 comentarios

Sinon aún no es compatible con las funciones de ES6. El trabajo en esto aún no ha comenzado, por lo que no sé cuándo funcionará.

@mantoni , correcto, esto fue más una solicitud de función :) ¿quieres dejarlo abierto, por ejemplo, con algún tipo de etiqueta ("es6"?) para que cuando se agregue a la hoja de ruta sea más fácil volver -evaluar lo que hay que hacer?

Esta sería una gran característica para tener.

@ruimarinho encontré esto https://github.com/ingameio/SinonES6.JS
reemplazar require("sinon") con require("sinon-es6") parece hacer que sus pruebas pasen

--
editado por @ fatso83 el 22 de junio de 2017:
Esto no parece ser cierto. Probé esto manualmente. De todos modos falla, lo que tiene mucho sentido ya que sinon-es6 _solo implementa su soporte para generadores para burlarse de _.

¿Alguien está trabajando en esto?

@emgeee no que yo sepa.

+1 @ruimarinho sinon.js con funciones es6 ¡sería genial tenerlo!

¿A @ingameio le importaría hacer un PR con sus cambios?

@ fatso83 gracias por la sugerencia!
Pronto haré las relaciones públicas ;)

Tengo problemas al ejecutar buster-test con la versión empaquetada. Hice un poco de depuración, pero tengo poco tiempo estos días, así que no pude resolver el problema.
Si alguien quiere ayudar, podría enviar los cambios a mi bifurcación, solo dímelo y lo haré.

@gaguirre Esa es una gran noticia. Maximillian acaba de convertir todas las pruebas a Mocha e hizo algunos cambios en la configuración de la prueba mientras lo hacía, así que tal vez las cosas funcionen si obtiene los últimos cambios de master .

@gaguirre En caso de que alguien tropiece con este hilo, creo que podría ser una idea impulsar los cambios en su bifurcación de todos modos para que un transeúnte pueda verlo.

@ fatso83 Empujé una primera versión a underscope/sinon .
El problema principal ocurre cuando se ejecutan las pruebas con un motor que no es compatible con es6 (phantomjs en este caso): falla al analizar el código, por lo que estoy usando eval() como solución alternativa, ya que puede estar rodeado por un intento/ captura. Creo que esto no es aceptable, pero es un punto de partida.

En este momento, estoy tratando de envolver la llamada del generador en otro archivo y requerirlo dinámicamente, pero no funciona, supongo que porque browserify está agrupando incluso los archivos requeridos dinámicamente. Me pregunto si algunos archivos podrían excluirse cuando se ejecuta npm run test-headless .

¿Cuál crees que podría ser la mejor solución?

Gracias por los comentarios, @gaguirre . Entonces, el problema básico es que necesitamos alguna forma de evitar el análisis si el motor no lo admite. Eso es un poco más difícil que la simple detección de características, como hacemos con WebWorkers, etc.

Simplemente delegar el cheque real es tan simple como if(require('generator-support')){ ... } , pero esto no soluciona el problema de análisis real.

¿Alguna idea sobre cómo lidiar limpiamente con esto, @mantoni y @mroderick?

PD: Me voy de vacaciones fuera de línea dentro de unas horas, así que no podré leer las respuestas hasta algún tiempo después de Semana Santa.

No puedo decir si este nivel de stubing del generador realmente funciona (particularmente en escenarios orientados a la corrutina); parece que podría estar en orden alguna emulación del comportamiento asíncrono. Pero creo que hay una forma de evitar el problema del análisis.

Sugerencia :

Extraiga el código del generador en un archivo separado y require() ese archivo solo a pedido cuando envuelva una función de generador, es decir:

?/es6-soporte.js:

"use strict";

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

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

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

(También haga lo mismo con las pruebas unitarias y asegúrese de que el seguimiento de la cobertura del código no haga que el archivo 'es6-support' se incluya automáticamente).

Sugerencia alternativa :

¿La compatibilidad con ES5 seguirá siendo un objetivo valioso para cuando sinon 2.0 esté listo para su lanzamiento? Tal vez sea hora de decirles a las pocas personas que aún admiten entornos heredados de ES5 únicamente sin el usuario de transpiladores que se quedarán atrás en 1.x.

@evan-king Ese requisito condicional no es una solución, ya que se interrumpirá en las compilaciones de nuestro navegador. Como los requisitos condicionales anulan el análisis estático del gráfico de dependencia, las herramientas como WebPack, Rollup y Browserify no podrán manejarlo. Está totalmente dentro o totalmente fuera.

Y sí, la compatibilidad con ES5 es una cosa. El soporte del generador ES6 es de mala calidad en el mejor de los casos, y obligar a las personas a usar herramientas adicionales para usar Sinon en sus proyectos elevará el nivel para realizar pruebas. Y para muchos, ese umbral es lo suficientemente alto tal como es. Ser capaz de incluir simplemente una etiqueta de secuencia de comandos para que funcione es una característica importante en mi humilde opinión. Es posible que rompamos la compatibilidad con ES5 en algún momento, pero no está en nuestra hoja de ruta y ni siquiera se ha discutido para Sinon 3. En efecto, Sinon 2 ha estado fuera durante bastante tiempo; solo hay algunas molestias menores que nos impiden hacer un lanzamiento oficial.

Básicamente, hay dos formas de obtener compatibilidad con ES6 sin romper la compatibilidad existente con el tiempo de ejecución de ES5 que puedo encontrar, y no estoy muy seguro acerca de la última (no lo he probado):

Evaluación condicional de módulos

Este es un truco, pero es bastante simple. Lo que significa es que podemos confiar en la integración de activos estáticos y las pruebas de características de ES6 para decidir si evaluar el módulo requerido. Esto garantizará la compatibilidad con ES5 (Sinon solo se parcheará si el tiempo de ejecución es compatible con la sintaxis), no hay necesidad de herramientas adicionales en forma de Babel ni del lado del creador de la biblioteca ni del usuario del cliente, y probar ES6 romperá los navegadores ES5 que habría fallado en cualquier caso.

Suponiendo que algo como brfs esté en su lugar, el código se vería así:

_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"); }
}

Babelificando a Sinon

Este es el que no estoy seguro porque en realidad no lo he probado. Si transpilamos la compilación a través de Babel a ES5, podemos escribir el código ES6 por todas partes, evitar saltar aros como lo hice anteriormente, y aún podemos usar los mismos tipos de comprobaciones para los generadores. Solo se implementarán utilizando construcciones ES5. Por supuesto, probar ES6 en navegadores ES5 seguirá fallando. Esto tiene la misma ventaja que el anterior en el lado del cliente, pero podríamos obstaculizar las contribuciones ya que el conocimiento de ES2015 en cosas como yield , async , function*() está lejos de alcanzar una amplia audiencia.

+1

@rpavlovs , realmente no tiene sentido agregar +1 al hilo. La interfaz de usuario de GitHub tiene un botón "agregar reacción" en la parte superior de cada comentario si necesita expresar sus emociones. Un +1 no hará nada. Una solicitud de extracción que implemente una de las sugerencias anteriores (o algo más inteligente), por otro lado, tiene muchas más posibilidades de solucionar este problema 😄

Me gustaría recibir información sobre cómo se vería el soporte de API para generadores , ya que después de usar un par de horas en esto, todavía no estoy seguro de lo que a la gente le gustaría ver.

Para comenzar a desarrollar esto, he creado una nueva rama que contiene las modificaciones de @ingameio a la API simulada, mientras que al mismo tiempo no rompe la compatibilidad con ES5 (usando el truco mencionado anteriormente).

Lo que me molesta un poco es que realmente no puedo decir cómo probar los cambios originales de Ingameio, ya que ninguno de los ejemplos de prueba funciona, ni siquiera el ejemplo en la bifurcación está completo, y no puedo hacer cosas para romper antes/después cambios.

Los generadores son cosas simples: seres suaves sincrónicos que recuerdan su pasado. Así que, por favor, no mezcle ningún ejemplo con co y otras cosas que no están relacionadas, ya que hace que sea más difícil ver lo que se quiere o lo que no funciona. El ejemplo superior, por ejemplo, es bastante complicado y también parece confundir lo que hace yield , ya que espera que el valor de retorno de la "expresión de rendimiento" sea el mismo que el "valor de rendimiento". El result del rendimiento es el valor pasado al next() del generador ( MDN )

Me doy cuenta de que los ejemplos que usan co probablemente solo lo usen para poder usar yield directamente en la prueba de Mocha, pero simplemente envuelva su ejemplo en un IIFE o alguna otra forma de lograr lo mismo para en aras de una mayor claridad.

Esta es una prueba simple de cómo los generadores admiten (funciona en el Sinon de hoy):

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

Ahora, ¿qué extensiones de API nos gustaría?
De la prueba original, asumo que nos gustaría ver algo como

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

cc @ruimarinho

Estoy cerrando este problema, ya que la prueba original tuvo un error y no puedo ver ningún problema con el manejo del generador en Sinon.

Únase a la discusión sobre cómo se vería una API para tratar con generadores (y sus iteradores asociados) en el problema n.º 1467

¿Fue útil esta página
0 / 5 - 0 calificaciones