sinon.stub
/ sandbox.stub
se tornou a pia da cozinha
comportamento configurável com problemas que geralmente são difíceis de encontrar e corrigir sem regressões.
Eu acho que a causa raiz das dificuldades é que stub
tem muitas responsabilidades.
Além disso, stub
também tem um uso problemático causado pelo fato de que o comportamento é definido após ter sido criado e pode ser redefinido várias vezes.
var myStub;
beforeEach(function(){
myStub = sinon.stub().resolves('apple pie :)');
});
// several hundred lines of tests later
myStub = sinon.stub().rejects('no more pie :(');
// several hundred lines of tests later
// what behaviour does myStub currently have? Can you tell without
// reading the entire file?
// can you safely change the behaviour without affecting tests further
// down in the file?
E depois há os cenários mais confusos
var myStub = sinon.stub()
.withArgs(42)
.onThirdCall()
.resolves('apple pie')
.rejects('no more pie')
O que isso faz mesmo?
Em vez de continuar a adicionar mais responsabilidades a stub
, proponho que, em vez disso, apresentemos novos membros a sinon
, que têm um escopo muito mais restrito .
O mais importante que posso pensar seria um substituto imutável para uma função.
Podemos então descobrir mais tarde o que vamos fazer com as propriedades (como um membro de responsabilidade novo, separado e único).
sinon.fake
Um fake
(o valor de retorno de chamar sinon.fake
) é um Function
puro e imutável . Ele faz uma coisa, e uma coisa só. Ele tem o mesmo comportamento em cada chamada. Ao contrário stub
, seu comportamento não pode ser redefinido. Se você precisar de um comportamento diferente, faça um novo fake
.
Uma falsificação pode ter uma dessas responsabilidades
Promise
para um valorPromise
para um Error
Error
Se você quer/precisa de efeitos colaterais, e ainda quer a interface do espião, então use a função real, use um stub
ou faça uma função personalizada
sinon.replace(myObject, myMethod, sandbox.spy(function(args) {
someFunctionWithSideEffects(args);
});
Será generoso ao lançar erros quando o usuário tentar criá-los/usá-los de maneiras não suportadas.
// will throw TypeError when `config` argument has more than one property
const fake = sinon.fake({
resolves: true,
returns: true
});
Exceto .withArgs
, pois isso viola a imutabilidade
// will return a Promise that resolves to 'apple pie'
var fake = sinon.fake({resolves: 'apple pie'})
// will return a Promise that rejects with the provided Error, or
// creates a generic Error using the input as message
var fake = sinon.fake({rejects: new TypeError('no more pie')});
var fake = sinon.fake({rejects: 'no more pie'});
// returns the value passed
var fake = sinon.fake({returns: 'apple pie'});
// throws the provided Error, or creates a generic Error using the
// input as message
var fake = sinon.fake({throws: new RangeError('no more pie')});
var fake = sinon.fake({throws: 'no more pie'});
// replace a method with a fake
var fake = sinon.replace(myObject, 'methodName', sandbox.fake({
returns: 'apple pie'
}))
// .. or use the helper method, which will use `sandbox.replace` and `
// sinon.fake`
var fake = sinon.setFake(global, 'methodName', {
returns: 'apple pie'
});
// create an async fake
var asyncFake = sinon.asyncFake({
returns: 'apple pie'
});
Não sei se fake
é o melhor substantivo para usar aqui, mas acho que devemos tentar manter a convenção de usar substantivos, e não nos desviarmos para adjetivos ou verbos.
Isso é algo que venho considerando há algum tempo, por que não criamos uma sandbox padrão? Se as pessoas precisarem de sandboxes separadas, elas ainda poderão criá-las.
Devemos criar um sandbox padrão que seja usado para todos os métodos expostos via sinon.*
.
Isso significa que sinon.stub
se tornará o mesmo que sandbox.stub
, o que removerá a limitação de poder stub propriedades usando sinon.stub
.
sandbox.replace
Crie sandbox.replace
e use isso para todas as operações que substituem qualquer coisa em qualquer lugar. Exponha isso como sinon.replace
e use o sandbox padrão quando usado dessa maneira.
Isso provavelmente deve ter alguma validação de entrada séria, então só substituirá funções por funções, acessadores por acessadores, etc.
Ping @sinonjs/core
Boas sugestões, Morgana. Obrigado por trazer isso à tona. Também acho que a API stub
é uma confusão e gosto de todas as suas sugestões. Aqui estão alguns pensamentos:
sinon.fake
Concordo que a imutabilidade é a chave aqui. No entanto, poderíamos permitir alguns casos de uso "sadios" que atualmente são possíveis com stubs.
Por exemplo, pode ser um caso de uso válido para render e retornar:
sinon.fake({
yields: [null, 42],
returns: true
})
Podemos verificar o que faz sentido e o que não faz.
Além disso, se dermos suporte callsThrough: true
como uma configuração (que é inválida em combinação com qualquer uma das propriedades de comportamento), as novas falsificações também podem ser usadas em vez da API "spy". Isso seria mais autoexplicativo do que aprender o que "spy" e "stub" significam em Sinon-speak 😄
Usar uma caixa de areia padrão
Embora eu goste dessa ideia, isso significa que chamar sinon.restore()
após um teste pode reverter algumas sobras de outros testes e levar a resultados surpreendentes - ou testes falhos que funcionaram antes. A coisa brilhante que isso permitiria é redefinir o sandbox global em beforeEach
para melhorar o isolamento do teste. 👍
caixa de areia.substituir
Eu gosto muito disso. Eu entendo isso como um utilitário "deixe-me colocar essa coisa aí", certo?
Além disso, se dermos suporte a callsThrough: true como uma configuração (que é inválida em combinação com qualquer uma das propriedades de comportamento), as novas falsificações também podem ser usadas em vez da API "spy". Isso seria mais autoexplicativo do que aprender o que "spy" e "stub" significam em Sinon-speak 😄
Isso significaria que não precisaríamos de spy
ou stub
?
caixa de areia.substituir
Eu gosto muito disso. Eu entendo isso como um utilitário "deixe-me colocar essa coisa aí", certo?
Sim, essa era a ideia. Em vez de sobrecarregar o mesmo método ( sinon.stub
) para fazer muitas, muitas coisas, tenha métodos explícitos que fazem apenas uma coisa
Como você destacou, a API fake
provavelmente não suportará tudo o que é possível atualmente com espiões e stubs. Mas sim, acho que a API fake
é uma oportunidade de unificar as funcionalidades stub
e spy
.
Embora eu goste dessa ideia, isso significa que chamar sinon.restore() após um teste pode reverter algumas sobras de outros testes e levar a resultados surpreendentes - ou testes falhos que funcionaram antes. A coisa brilhante que isso permitiria é redefinir o sandbox global em beforeEach para melhorar o isolamento do teste. 👍
É certamente uma mudança de ruptura, e não deve ser introduzida de ânimo leve.
Ao criar um fake
, se você não passar uma configuração de comportamento, seria equivalente a um spy
.
// ~spy, records all calls, has no behaviour
const fake = sinon.fake();
// ~stub, records all calls, returns 'apple pie'
const fake = sinon.fake({
returns: 'apple pie'
});
Como você criaria um stub que não faz nada então?
Como você criaria um stub que não faz nada então?
Não sei se entendi bem sua pergunta, mas vamos lá
// a fake that has no behaviour
const fake = sinon.fake();
// put it in place of an existing method
sandbox.replace(myObject, 'someMethod', fake);
Ah, acho que entendi o que você quis dizer agora: A fake
é sempre um stub
. Quando você disse ~spy, records all calls
eu entendi "chamadas para a função original". No entanto, o fake
não tem conhecimento sobre a função que está substituindo – é isso que sandbox.replace
faz.
Então, com isso em mente, aqui está outra proposta de como poderíamos dobrar a atual funcionalidade spy
(como fazer chamadas) nas novas falsificações:
const fake = sinon.fake(function () {
// Any custom function
});
A função dada seria chamada pelo fake. Essa API torna impossível misturá-la com outros comportamentos. Na verdade, um objeto de configuração criaria uma função que implementa o comportamento especificado e depois o passa para o fake.
A implementação sandbox.spy(object, method)
poderia então se tornar isso:
const original = object[method];
const fake = sinon.fake(original);
sandbox.replace(object, method, fake);
Basicamente um one-liner 🤓
Sim. Depois de simplificar as coisas, você pode começar a remixar por diversão 🎉 e lucrar 💰
No entanto, se quisermos gravitar apenas usando fake
e não mais usando spy
e stub
, provavelmente devemos deixar esses dois em paz.
Estou pensando na "próxima" API aqui. Você precisaria de sandbox.spy
para ter a lógica de substituição em algum lugar. Pelo que entendi, isso deve ser compatível com versões anteriores. A implementação stub
poderia então ser preterida.
Você precisaria do sandbox.spy para ter a lógica de substituição em algum lugar. Pelo que entendi, isso deve ser compatível com versões anteriores. A implementação de stub pode então ser preterida.
Não tenho certeza se sigo. Você poderia detalhar?
Certo. Pelo que entendi, você quer um substituto para a API stub
complicada demais. A maneira como os stubs são implementados atualmente é criando um spy
com uma função que implementa o comportamento. O que estou sugerindo é fazer a mesma coisa com a API fake
e criar internamente um spy
, mas não retornaríamos mais um comportamento, pois queremos nos livrar do encadeamento . Devolveríamos o espião. Isso torna a implementação fake
uma alternativa para stub
com a função retornada sendo compatível com todas as APIs Sinon atuais. Isso faz sentido ou estou perdendo alguma coisa?
OK, acho que temos um entendimento semelhante 👍
Apenas para reiterar, caso tenhamos perdido alguma coisa e para que outros colaboradores tenham o mesmo entendimento.
sandbox.replace
(atualmente está em stub
)sinon
terá um sandbox padrão, permitindo sinon.reset
e sinon.restore
(devemos apenas mesclar esses?)sinon.fake
— um substituto imutável e programável para funções que gravam todas as chamadassinon.spy
sinon.stub
// effectively a spy that has no target
const fake = sinon.fake()
// spy on a function
const fake = sinon.fake(console.log);
const fake = sinon.fake(function() { return 'apple pie'; });
// a shorthand construction of fake with behaviour
const fake = sinon.fake({
returns: 'apple pie'
});
// replacing an existing function with a fake
var fakeLog = sinon.fake();
sandbox.replace(console, 'log', fakeLog);
Nesse estado, a proposta trata apenas de Function
. Precisamos considerar o que fazer sobre propriedades e acessadores que não são de função. No mínimo, devemos ver se podemos limitar sandbox.replace
para permitir apenas substituições sãs.
Isso significa que sinon.stub()
e sinon.spy()
serão preteridos no futuro em favor de sinon.fake()
ou apenas refeitos internamente? Se sim, então estamos essencialmente nos movendo em direção ao pensamento de TestDouble . Não necessariamente uma coisa ruim, IMHO, mas pode valer a pena considerar que, se muitas pessoas acharem que precisarão substituir todas as chamadas de API Sinon de qualquer maneira por sinon.fake()
, elas também podem usar outra biblioteca (embora isso significaria que eles perderiam todo o conhecimento existente da API da Sinon).
Isso significa que sinon.stub() e sinon.spy() serão descontinuados no futuro em favor de sinon.fake(), ou apenas refeitos internamente? Se sim, então estamos essencialmente nos movendo em direção ao pensamento de TestDouble.
Acho que se sobrepõe um pouco. Minha principal motivação para esta proposta é ter funções falsas com comportamento imutável.
Não necessariamente uma coisa ruim, IMHO, mas pode valer a pena considerar que, se muitas pessoas acharem que precisarão substituir todas as chamadas de API Sinon de qualquer maneira para sinon.fake(), elas também podem usar outra biblioteca (embora isso significaria que eles perderiam todo o conhecimento existente da API da Sinon).
Se as pessoas acharem que outra biblioteca atende melhor às suas necessidades, fico feliz por ajudá-las a aprender isso :)
Mas vamos manter os métodos spy e stub ou desativá-los, com algumas possíveis reduções de funcionalidade? Isso não estava claro para mim.
Mas vamos manter os métodos spy e stub ou desativá-los, com algumas possíveis reduções de funcionalidade?
Uma vez que fake
pareça estável, então eu descontinuaria spy
e stub
e, em seguida, daria um ano para permitir que as pessoas tivessem tempo para atualizar.
Acho que devemos tentar o nosso melhor para fornecer codemods e ótima documentação, para ajudar as pessoas a mover seu código
Estou trabalhando em uma ramificação para as primeiras partes deste (sandbox padrão). Eu refatorei o código para que sandbox
e collection
agora sejam um. Eu tenho o sandbox padrão funcionando.
Vou organizar os commits nos próximos dias e, em seguida, criar uma ramificação neste repositório para a API atualizada.
Esta é uma ótima idéia, muito bem escrita btw.
Eu também adicionaria avisos de descontinuação a stubs e espiões.
Eu também estava pensando em talvez mudar a passagem de um objeto com chaves passando funções.
Isso adicionaria os seguintes benefícios:
type
a essas funções para o usuário que deseja usar typescript
ou outro tipo de verificadores estáticosPortanto, a API ficaria assim:
// It would be cool to allow users to import these using destructuring to make code more concise
import { resolves, rejects, returns } from 'sinon/behaviors';
var fake = sinon.fake(resolves('apple pie'))
var fake = sinon.fake(rejects(new TypeError('no more pie')));
var fake = sinon.fake(rejects('no more pie'));
var fake = sinon.fake(returns('apple pie'));
var fake = sinon.fake(throws(new RangeError('no more pie'));
var fake = sinon.fake(throws('no more pie'));
Quando se trata de implementar isso, pode ser apenas uma questão de retornar objetos muito simples como os que você está propondo. Então, se tivermos mais de um comportamento, podemos apenas mesclá-los.
Além disso, quando se trata de misturar coisas como onThirdCall
e withArgs
, acho que o que acontece nesses casos deve ser documentado.
Desculpe por revisar isso tão tarde. Os últimos meses foram muito corridos.
@lucasfcosta confira o PR #1586
Este problema foi marcado automaticamente como obsoleto porque não teve atividade recente. Será fechado se não ocorrer mais nenhuma atividade. Obrigado por suas contribuições.
A versão anterior 5.0.0 está causando problemas com as versões de pré-lançamento 5.0.0-next.* posteriores em package.json porque 5.0.0 é maior do que qualquer versão de pré-lançamento.
Como o 5.0.0 está disponível, acho que os números de pré-lançamento de next
precisam ser aumentados talvez para 5.0.1-next.1
?
Percebi isso porque outro pacote que eu estava usando estava recebendo uma msg obsoleta e seu package.json depende de "sinon": "^5.0.0-next.4"
npm WARN deprecated [email protected]: this version has been deprecated
Eu não tinha certeza se isso era digno de abrir uma nova edição para um problema de pré-lançamento, então um comentário aqui parecia mais seguro.
Outra solução seria lançar a próxima versão principal. O que você acha @sinonjs/core?
@mroderick Não posso mais dizer quais são todas as mudanças para a v5. Dos meus últimos testes funcionou bem e estou ansioso para usar as novas falsificações. É um novo major, então ei, envie 😄
Há apenas mais um PR #1764 que eu gostaria de fundir, antes de lançarmos a próxima versão principal.
Publiquei [email protected]
, espero que isso facilite a vida das pessoas nesse meio tempo.
Obrigado, eu testei (sempre é bom verificar novamente) dependências em package.json e "sinon": "^5.0.1"
dá um erro como deveria porque não há correspondência encontrada (nenhuma versão ainda) e "sinon": "^5.0.1-next.1"
funciona corretamente obtendo essa versão.
Isso nunca foi grande coisa, só achei que valia a pena avisar, principalmente quando vi que a v5 estava em desenvolvimento há algum tempo, então não tinha certeza de quanto tempo até ser lançada. Acho que lançar em um futuro próximo parece uma boa ideia.
fake
foi introduzido com #1768, que se tornou [email protected]
Comentários muito úteis
OK, acho que temos um entendimento semelhante 👍
Apenas para reiterar, caso tenhamos perdido alguma coisa e para que outros colaboradores tenham o mesmo entendimento.
TL;DR
sandbox.replace
(atualmente está emstub
)sinon
terá um sandbox padrão, permitindosinon.reset
esinon.restore
(devemos apenas mesclar esses?)sinon.fake
— um substituto imutável e programável para funções que gravam todas as chamadassinon.spy
sinon.stub
Nesse estado, a proposta trata apenas de
Function
. Precisamos considerar o que fazer sobre propriedades e acessadores que não são de função. No mínimo, devemos ver se podemos limitarsandbox.replace
para permitir apenas substituições sãs.