Sinon: Espião `returnValue` não funciona com geradores

Criado em 3 jan. 2015  ·  20Comentários  ·  Fonte: sinonjs/sinon

Ao trabalhar com funções geradoras, o valor de retorno de um espião é sempre undefined . Aqui está um caso de teste com falha:

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

Corra com npm install co mocha should sinon && ./node_modules/.bin/mocha --harmony index.js .

2.x Unverified

Todos 20 comentários

A Sinon ainda não suporta recursos do ES6. O trabalho sobre isso ainda não começou, então não sei quando isso funcionará.

@mantoni , certo, isso foi mais uma solicitação de recurso :) você quer deixar aberto, por exemplo, com algum tipo de tag ("es6"?) -avaliar o que precisa ser feito?

Este seria um ótimo recurso para ter.

@ruimarinho achei isso https://github.com/ingameio/SinonES6.JS
substituir require("sinon") por require("sinon-es6") parece fazer seus testes passarem

--
editado por @fatso83 em 22 de junho de 2017:
Isso não parece ser verdade. Eu testei isso manualmente. Ele falha mesmo assim, o que faz todo o sentido, pois sinon-es6 _só implementa seu suporte para geradores para simulação _.

Alguém está trabalhando nisso?

@emgeee não que eu saiba.

+1 @ruimarinho sinon.js com recursos es6 seria ótimo ter!

O @ingameio se importa em fazer um PR com suas alterações?

@fatso83 obrigado pela sugestão!
Em breve farei o PR ;)

Estou tendo problemas ao executar o buster-test na versão empacotada. Fiz algumas depurações, mas estou com pouco tempo hoje, então não consegui resolver o problema.
Se alguém quiser ajudar, eu poderia enviar as alterações para o meu fork, apenas me diga e eu farei isso.

@gaguirre Isso é uma ótima notícia. Maximillian acabou de converter todos os testes para o Mocha e fez algumas alterações na configuração do teste enquanto fazia isso, então talvez as coisas funcionem se você fizer as últimas alterações de master ?

@gaguirre No caso de alguém tropeçar neste tópico, acho que pode ser uma ideia enviar as alterações para o seu fork de qualquer maneira para que um transeunte possa dar uma olhada nele.

@fatso83 Eu empurrei uma primeira versão para underscope/sinon .
O principal problema ocorre ao executar os testes com um mecanismo que não suporta es6 (phantomjs neste caso): ele falha ao analisar o código, então estou usando eval() como solução alternativa, pois pode ser cercado por um try/ pegar. Eu acho que isso não é aceitável, mas é um ponto de partida.

No momento, estou tentando envolver a chamada do gerador em outro arquivo e exigi-la dinamicamente, mas não está funcionando, acho que porque o browserify está agrupando até os arquivos necessários dinamicamente. Eu estou querendo saber se alguns arquivos podem ser excluídos ao executar npm run test-headless .

Qual você acha que poderia ser a melhor solução?

Obrigado pelo feedback, @gaguirre . Portanto, a questão básica é que precisamos de alguma maneira de evitar a análise se o mecanismo não o suportar. Isso é um pouco mais difícil do que a simples detecção de recursos, como fazemos com WebWorkers, etc.

Apenas delegar a verificação real é tão simples quanto if(require('generator-support')){ ... } , mas isso não corrige o problema real de análise.

Alguma ideia de como lidar com isso de forma limpa, @mantoni e @mroderick?

PS Estou fora de férias offline em algumas horas, então não poderei ler as respostas até algum tempo depois da Páscoa.

Não posso dizer se esse nível de stubbing de gerador realmente funciona (particularmente em cenários orientados a corrotina) - parece que alguma emulação de comportamento assíncrono pode estar em ordem. Mas eu acho que há uma maneira de contornar o problema de análise.

Sugestão :

Extraia o código do gerador em um arquivo separado e require() esse arquivo somente sob demanda ao agrupar uma função do gerador, ou seja:

?/es6-support.js:

"use strict";

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

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

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

(Também faça o mesmo com os testes de unidade e garanta que o rastreamento da cobertura do código não faça com que o arquivo 'es6-support' seja incluído automaticamente.)

Sugestão alternativa :

A compatibilidade com o ES5 ainda será um objetivo válido quando o sinon 2.0 estiver pronto para lançamento? Talvez seja hora de dizer às poucas pessoas que ainda suportam ambientes legados somente ES5 sem o usuário de transpiladores que eles serão deixados para trás no 1.x.

@evan-king Essa exigência condicional não é uma solução, pois quebrará em nossas compilações do navegador. Como a condicional exige anulação da análise estática do gráfico de dependências, ferramentas como WebPack, Rollup e Browserify não conseguirão lidar com isso. Ou está totalmente dentro ou totalmente fora.

E sim, a compatibilidade com ES5 é uma coisa. O suporte ao gerador ES6 é, na melhor das hipóteses, de má qualidade, e forçar as pessoas a usar ferramentas adicionais para usar o Sinon em seus projetos aumentará o nível de testes. E para muitos, esse limite já é alto o suficiente. Ser capaz de incluir uma tag de script simples para fazê-lo funcionar é um recurso importante do IMHO. Podemos quebrar a compatibilidade com o ES5 em algum momento, mas não está em nosso roteiro e nem foi discutido para o Sinon 3. O Sinon 2 foi lançado há algum tempo; há apenas alguns pequenos incômodos que nos impedem de fazer um lançamento oficial.

Existem basicamente duas maneiras de obter compatibilidade com o ES6 sem quebrar a compatibilidade de tempo de execução do ES5 existente que posso criar, e não tenho certeza sobre a última (não testei):

Avaliação condicional de módulos

Este é um hack, mas é bastante simples. O que isso significa é que podemos confiar no inlining de ativos estáticos e no teste de recursos do ES6 para decidir se devemos avaliar o módulo necessário. Isso garantirá a compatibilidade com o ES5 (o Sinon só será corrigido se o tempo de execução suportar a sintaxe), não há necessidade de ferramentas adicionais na forma de Babel no lado do criador da biblioteca nem no usuário do cliente, e testar o ES6 quebrará os navegadores ES5 que teria falhado em qualquer caso.

Supondo que algo como brfs esteja em vigor, o código seria algo assim:

_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 Sinon

Este é o que eu não tenho certeza porque eu realmente não tentei. Se transpilarmos a compilação através do Babel para o ES5, podemos escrever código ES6 em todos os lugares, evitando pular obstáculos como eu fiz acima, e ainda podemos usar os mesmos tipos de verificações para geradores. Eles serão implementados apenas usando construções ES5. É claro que testar o ES6 em navegadores ES5 ainda falhará. Isso tem a mesma vantagem que o anterior no lado do cliente, mas podemos dificultar as contribuições, pois o conhecimento do ES2015 em coisas como yield , async , function*() está longe de alcançar um grande público.

+1

@rpavlovs , não faz sentido adicionar +1 ao tópico. A IU do GitHub tem um botão "adicionar reação" na parte superior de cada comentário, se você precisar expressar suas emoções. Um +1 não fará nada. Um pull request que implementa uma das sugestões acima (ou algo mais inteligente), por outro lado, tem uma chance muito maior de corrigir esse problema 😄

Gostaria de informações sobre como seria o suporte de API para geradores , pois depois de usar algumas horas nisso, ainda não tenho certeza do que as pessoas gostariam de ver.

Para começar a desenvolver isso, criei uma nova ramificação que contém as modificações do @ingameio na API simulada, sem quebrar a compatibilidade do ES5 (usando o hack mencionado acima).

O que me irrita um pouco é que eu realmente não sei como testar as alterações originais do Ingameio, pois nenhum dos exemplos de teste funciona - nem mesmo o exemplo no fork está completo, e não consigo fazer as coisas quebrarem pré/pós alterar.

Geradores são coisas simples: seres síncronos gentis que lembram seu passado. Então, por favor, não brinque com nenhum exemplo com co e outras coisas que não estão relacionadas, pois torna mais difícil ver o que é desejado/não funciona. O exemplo principal, por exemplo, é bastante complicado e também parece confundir o que yield faz, pois espera que o valor de retorno da "expressão de rendimento" seja o mesmo que o "valor de rendimento". O result do rendimento é o valor passado para o next() ( MDN ) do gerador

Eu percebo que os exemplos usando co provavelmente só o usam para poder usar yield diretamente no teste Mocha, mas apenas envolva seu exemplo em um IIFE ou alguma outra maneira de obter o mesmo para para maior clareza.

Este é um teste simples de como os geradores suportam (funciona no Sinon de hoje):

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

Agora, quais extensões de API gostaríamos?
A partir do teste original, suponho que gostaríamos de ver algo como

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

cc @ruimarinho

Estou encerrando este problema, pois o teste original teve um erro e não consigo ver nenhum problema com o manuseio do gerador no Sinon.

Junte-se à discussão sobre como uma API para lidar com geradores (e seus iteradores associados) ficaria no problema #1467

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

byohay picture byohay  ·  3Comentários

stevenmusumeche picture stevenmusumeche  ·  3Comentários

kbirger picture kbirger  ·  3Comentários

OscarF picture OscarF  ·  4Comentários

optimatex picture optimatex  ·  4Comentários