Sinon: withArgs no funciona correctamente con sinon.mock

Creado en 30 ago. 2017  ·  3Comentarios  ·  Fuente: sinonjs/sinon

  • Versión de Sinon: 3.2.1
  • Entorno: OSX, Nodo 7
  • Otras bibliotecas que está utilizando: Mocha, mjackson / esperan

Qué esperabas que sucediera?
Cuando creo un simulacro en una clase e intento controlar el comportamiento de una de sus funciones usando withArgs , se lanza una excepción. Ocurre solo cuando se utilizan varias llamadas de withArgs .

Que pasa realmente

Como reproducir


Ejemplo reproducible

Supongamos que declaré una clase C :

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

Intento controlar el comportamiento de foo usando un simulacro en 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();
      });

Lo que arroja la siguiente excepción:

ExpectationError: foo recibió argumentos incorrectos ["a", "b"], esperado ["c", "d"]
en Object.fail (node_modules / sinon / lib / sinon / mock-expectation.js: 281: 25)
en función.(módulos_nodo / sinon / lib / sinon / mock-expectation.js: 182: 33)
en Array.forEach (nativo)
en Function.verifyCallAllowed (node_modules / sinon / lib / sinon / mock-expectation.js: 175: 27)
en Function.invoke (node_modules / sinon / lib / sinon / mock-expectation.js: 78: 14)
en el proxy (node_modules / sinon / lib / sinon / spy.js: 89: 22)

Mientras 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 la pena mencionar que los siguientes escenarios funcionan como se esperaba:

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

Es decir, el problema ocurre solo cuando se usa withArgs varias veces.
Puede estar relacionado con # 1381, pero como puede ver en el ejemplo, la solución sugerida allí no funciona.

Bug Medium Help wanted stale

Comentario más útil

Eché un vistazo al código.
En primer lugar, esta prueba 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();
});

Hay dos razones por las que esto funciona y la prueba del problema original no:

  • Creo dos expects .
  • Llamo al método estático en lugar del devuelto por expects .

Es decir, esto tampoco 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 sucede?

Cada vez que se llama a expects , sucede lo siguiente:

  • Se crea una nueva expectativa (código auxiliar mejorado con minCalls/maxCalls , que son las variables que se cambian cuando se llama a once() por ejemplo), solo que es un "código auxiliar con ámbito", no tener conexión con otros expects . Es el objeto devuelto por expects .
  • Se agrega a la lista de expectativas.

Lo que da como resultado diferentes comportamientos para estos escenarios:

  • Si llama al método devuelto por expects , estará limitado solo a ese expects .
  • Si llama al método directamente y no al código auxiliar, buscará en todos los expects y encontrará uno que se ajuste. Si hay más de uno, llamará al primero.

¿Es este el comportamiento correcto? Me parece más intuitivo que una vez que alguien llama a expects en un método, se comportará exactamente como stub (con los minCalls/maxCalls adicionales). Esto resultará en:

  • Se puede compartir más código entre stub y mock-expectation .
  • El comportamiento de los escenarios anteriores será el mismo.

Ahora, ¿qué pasaría con minCalls/maxCalls ?

Dado que ahora solo hay un expectation , propongo tres formas alternativas de realizar la verificación del recuento de llamadas con simulacros:

  • 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.

Aunque puede ser un poco confuso.

  • No 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();

Es más claro, pero no se puede copiar foo más de una vez, por lo que necesitaremos una forma de evitarlo. Además, esto todavía es posible:

  CMock.expects('foo').withArgs('a', 'b').returns(1).withArgs('c', 'd').returns(2).twice(); // I guess that this should only affect the last `withArgs`?
  • Utilice Spy API que recuerda la cantidad de llamadas.

Todos 3 comentarios

Gracias por un informe ejemplar tema. No uso la funcionalidad de simulacros, ya que hace que me duela el cerebro, por lo que alguien más tendrá que echarle un vistazo a esto, pero tus ejemplos deberían facilitarlo mucho.

Siéntase libre de echar un vistazo al código usted mismo, a menudo la forma más rápida de solucionarlo :-)

Eché un vistazo al código.
En primer lugar, esta prueba 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();
});

Hay dos razones por las que esto funciona y la prueba del problema original no:

  • Creo dos expects .
  • Llamo al método estático en lugar del devuelto por expects .

Es decir, esto tampoco 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 sucede?

Cada vez que se llama a expects , sucede lo siguiente:

  • Se crea una nueva expectativa (código auxiliar mejorado con minCalls/maxCalls , que son las variables que se cambian cuando se llama a once() por ejemplo), solo que es un "código auxiliar con ámbito", no tener conexión con otros expects . Es el objeto devuelto por expects .
  • Se agrega a la lista de expectativas.

Lo que da como resultado diferentes comportamientos para estos escenarios:

  • Si llama al método devuelto por expects , estará limitado solo a ese expects .
  • Si llama al método directamente y no al código auxiliar, buscará en todos los expects y encontrará uno que se ajuste. Si hay más de uno, llamará al primero.

¿Es este el comportamiento correcto? Me parece más intuitivo que una vez que alguien llama a expects en un método, se comportará exactamente como stub (con los minCalls/maxCalls adicionales). Esto resultará en:

  • Se puede compartir más código entre stub y mock-expectation .
  • El comportamiento de los escenarios anteriores será el mismo.

Ahora, ¿qué pasaría con minCalls/maxCalls ?

Dado que ahora solo hay un expectation , propongo tres formas alternativas de realizar la verificación del recuento de llamadas con simulacros:

  • 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.

Aunque puede ser un poco confuso.

  • No 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();

Es más claro, pero no se puede copiar foo más de una vez, por lo que necesitaremos una forma de evitarlo. Además, esto todavía es posible:

  CMock.expects('foo').withArgs('a', 'b').returns(1).withArgs('c', 'd').returns(2).twice(); // I guess that this should only affect the last `withArgs`?
  • Utilice Spy API que recuerda la cantidad de llamadas.

Este problema se ha marcado automáticamente como obsoleto porque no ha tenido actividad reciente. Se cerrará si no se produce más actividad. Gracias por sus aportaciones.

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