Sinon: withArgs não funciona corretamente com sinon.mock

Criado em 30 ago. 2017  ·  3Comentários  ·  Fonte: sinonjs/sinon

  • Versão Sinon: 3.2.1
  • Ambiente: OSX, Nó 7
  • Outras bibliotecas que você está usando: Mocha, mjackson / expect

O que você esperava que fosse acontecer?
Quando eu crio um mock em uma classe e tento controlar o comportamento de uma de suas funções usando withArgs , uma exceção é lançada. Acontece apenas ao usar várias chamadas de withArgs .

O que realmente acontece

Como reproduzir


Exemplo reproduzível

Suponha que eu declare uma classe C :

      class C {
        static foo(arg1, arg2) {return 'foo';}
      }

Tento controlar o comportamento de foo usando uma simulação em C :

      it('test withArgs mock', () => {
        let CMock = sinon.mock(C);
        let fooStub = CMock.expects('foo');

        fooStub.withArgs('a', 'b').returns(1);
        fooStub.withArgs('c', 'd').returns(2);

        expect(fooStub('a', 'b')).toEqual(1);
        expect(fooStub('c', 'd')).toEqual(2);

        CMock.restore();
      });

O que lança a seguinte exceção:

ExpectationError: foo recebeu argumentos errados ["a", "b"], esperado ["c", "d"]
em Object.fail (node_modules / sinon / lib / sinon / mock-expectation.js: 281: 25)
na função.(node_modules / sinon / lib / sinon / mock-expectation.js: 182: 33)
em Array.forEach (nativo)
em Function.verifyCallAllowed (node_modules / sinon / lib / sinon / mock-expectation.js: 175: 27)
em Function.invoke (node_modules / sinon / lib / sinon / mock-expectation.js: 78: 14)
no proxy (node_modules / sinon / lib / sinon / spy.js: 89: 22)

Enquanto este código funciona:

      it('test withArgs stub', () => {
        let fooStub = sinon.stub(C, 'foo');

        fooStub.withArgs('a', 'b').returns(1);
        fooStub.withArgs('c', 'd').returns(2);

        expect(fooStub('a', 'b')).toEqual(1);
        expect(fooStub('c', 'd')).toEqual(2);

        fooStub.restore();
      });

Vale ressaltar que os seguintes cenários funcionam conforme o esperado:

      it('test one withArgs mock', () => {
        let CMock = sinon.mock(C);
        let fooStub = CMock.expects('foo');

        fooStub.withArgs('a', 'b').returns(1);

        expect(fooStub('a', 'b')).toEqual(1); // ok

        CMock.restore();
      });
      it('test one withArgs mock', () => {
        let CMock = sinon.mock(C);
        let fooStub = CMock.expects('foo');

        fooStub.withArgs('a', 'b').returns(1);

        expect(fooStub('c', 'd')).toEqual(1); // error: foo received wrong arguments ["c", "d"], expected ["a", "b"]

        CMock.restore();
      });

Ou seja, o problema ocorre apenas ao usar withArgs várias vezes.
Pode estar relacionado ao # 1381, mas como você pode ver no exemplo, a solução sugerida não funciona.

Bug Medium Help wanted stale

Comentários muito úteis

Eu dei uma olhada no código.
Em primeiro lugar, este teste funciona:

class C {
  static foo(arg1, arg2) {return 'foo';}
}

it('test withArgs mock', () => {
  let CMock = sinon.mock(C);

  CMock.expects('foo').withArgs('a', 'b').returns(1);
  CMock.expects('foo').withArgs('c', 'd').returns(2);

  expect(C.foo('a', 'b')).toEqual(1);
  expect(C.foo('c', 'd')).toEqual(2);

  CMock.restore();
});

Existem dois motivos pelos quais isso funciona e o teste do problema original não:

  • Eu crio dois expects .
  • Eu chamo o método estático em vez daquele retornado por expects .

Ou seja, isso também não funciona:

it('test withArgs mock', () => {
  let CMock = sinon.mock(C);

  CMock.expects('foo').withArgs('a', 'b').returns(1);
  let fooStub = CMock.expects('foo').withArgs('c', 'd').returns(2);

  expect(fooStub('a', 'b')).toEqual(1); // Error: foo received wrong arguments ["a", "b"], expected ["c", "d"]
  expect(fooStub('c', 'd')).toEqual(2);

  CMock.restore();
});

Por que isso acontece?

Cada vez que expects é chamado, acontece o seguinte:

  • Uma nova expectativa (stub aprimorado com minCalls/maxCalls - que são as variáveis ​​que são alteradas quando você chama once() por exemplo) é criada, apenas que é um "stub com escopo", não tem conexão com outro expects . É o objeto retornado de expects .
  • É adicionado à lista de expectativas.

O que resulta em comportamentos diferentes para esses cenários:

  • Se você chamar o método retornado por expects , ele será limitado apenas a expects .
  • Se você chamar o método diretamente e não o esboço, ele pesquisará todos os expects e encontrará um que se encaixe. Se houver mais de um, ele chamará o primeiro.

Este é o comportamento correto? Parece-me mais intuitivo que uma vez que alguém chame expects em um método, ele se comportará exatamente como stub (com o minCalls/maxCalls ). Isso resultará em:

  • Mais código pode ser compartilhado entre stub e mock-expectation .
  • O comportamento dos cenários acima será o mesmo.

Agora, o que aconteceria com minCalls/maxCalls ?

Como há apenas um expectation agora, proponho três maneiras alternativas de fazer a verificação da contagem de chamadas com simulações:

  • Permitir concat once() , twice() etc.
  let fooStub = CMock.expects('foo').withArgs('a', 'b').returns(1).twice();
  fooStub.withArgs('c', 'd').returns(2).once(); // Does not affect the previous expectation.

Pode ser um pouco confuso.

  • Não permita concat once() , twice() etc.
  CMock.expects('foo').withArgs('a', 'b').returns(1).twice(); // `twice()` returns `null`, so that one has to call `expects()` again to set expectations for other arguments. 
  CMock.expects('foo').withArgs('c', 'd').returns(2).once();

Mais claro, mas você não pode stub foo mais de uma vez, portanto, precisaremos encontrar uma maneira de contornar isso. Além disso, isso ainda é possível:

  CMock.expects('foo').withArgs('a', 'b').returns(1).withArgs('c', 'd').returns(2).twice(); // I guess that this should only affect the last `withArgs`?
  • Use Spy API que lembra o número de chamadas.

Todos 3 comentários

Obrigado por um relatório questão exemplar. Eu não uso a funcionalidade de simulação, pois faz meu cérebro doer, então alguém precisará dar uma olhada nisso, mas seus exemplos devem tornar isso muito mais fácil.

Sinta-se à vontade para dar uma olhada no código - geralmente a maneira mais rápida de consertar isso :-)

Eu dei uma olhada no código.
Em primeiro lugar, este teste funciona:

class C {
  static foo(arg1, arg2) {return 'foo';}
}

it('test withArgs mock', () => {
  let CMock = sinon.mock(C);

  CMock.expects('foo').withArgs('a', 'b').returns(1);
  CMock.expects('foo').withArgs('c', 'd').returns(2);

  expect(C.foo('a', 'b')).toEqual(1);
  expect(C.foo('c', 'd')).toEqual(2);

  CMock.restore();
});

Existem dois motivos pelos quais isso funciona e o teste do problema original não:

  • Eu crio dois expects .
  • Eu chamo o método estático em vez daquele retornado por expects .

Ou seja, isso também não funciona:

it('test withArgs mock', () => {
  let CMock = sinon.mock(C);

  CMock.expects('foo').withArgs('a', 'b').returns(1);
  let fooStub = CMock.expects('foo').withArgs('c', 'd').returns(2);

  expect(fooStub('a', 'b')).toEqual(1); // Error: foo received wrong arguments ["a", "b"], expected ["c", "d"]
  expect(fooStub('c', 'd')).toEqual(2);

  CMock.restore();
});

Por que isso acontece?

Cada vez que expects é chamado, acontece o seguinte:

  • Uma nova expectativa (stub aprimorado com minCalls/maxCalls - que são as variáveis ​​que são alteradas quando você chama once() por exemplo) é criada, apenas que é um "stub com escopo", não tem conexão com outro expects . É o objeto retornado de expects .
  • É adicionado à lista de expectativas.

O que resulta em comportamentos diferentes para esses cenários:

  • Se você chamar o método retornado por expects , ele será limitado apenas a expects .
  • Se você chamar o método diretamente e não o esboço, ele pesquisará todos os expects e encontrará um que se encaixe. Se houver mais de um, ele chamará o primeiro.

Este é o comportamento correto? Parece-me mais intuitivo que uma vez que alguém chame expects em um método, ele se comportará exatamente como stub (com o minCalls/maxCalls ). Isso resultará em:

  • Mais código pode ser compartilhado entre stub e mock-expectation .
  • O comportamento dos cenários acima será o mesmo.

Agora, o que aconteceria com minCalls/maxCalls ?

Como há apenas um expectation agora, proponho três maneiras alternativas de fazer a verificação da contagem de chamadas com simulações:

  • Permitir concat once() , twice() etc.
  let fooStub = CMock.expects('foo').withArgs('a', 'b').returns(1).twice();
  fooStub.withArgs('c', 'd').returns(2).once(); // Does not affect the previous expectation.

Pode ser um pouco confuso.

  • Não permita concat once() , twice() etc.
  CMock.expects('foo').withArgs('a', 'b').returns(1).twice(); // `twice()` returns `null`, so that one has to call `expects()` again to set expectations for other arguments. 
  CMock.expects('foo').withArgs('c', 'd').returns(2).once();

Mais claro, mas você não pode stub foo mais de uma vez, portanto, precisaremos encontrar uma maneira de contornar isso. Além disso, isso ainda é possível:

  CMock.expects('foo').withArgs('a', 'b').returns(1).withArgs('c', 'd').returns(2).twice(); // I guess that this should only affect the last `withArgs`?
  • Use Spy API que lembra o número de chamadas.

Este problema foi marcado automaticamente como obsoleto porque não teve atividades recentes. Ele será fechado se nenhuma outra atividade ocorrer. Obrigado por suas contribuições.

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