Mocha / Chai expect.to.throw no atrapa errores arrojados

259

Tengo problemas para que Chai expect.to.throwfuncione en una prueba para mi aplicación node.js. La prueba sigue fallando en el error arrojado, pero si envuelvo el caso de prueba en try y catch y afirmo sobre el error capturado, funciona.

¿ expect.to.throwNo funciona como creo que debería o algo así?

it('should throw an error if you try to get an undefined property', function (done) {
  var params = { a: 'test', b: 'test', c: 'test' };
  var model = new TestModel(MOCK_REQUEST, params);

  // neither of these work
  expect(model.get('z')).to.throw('Property does not exist in model schema.');
  expect(model.get('z')).to.throw(new Error('Property does not exist in model schema.'));

  // this works
  try { 
    model.get('z'); 
  }
  catch(err) {
    expect(err).to.eql(new Error('Property does not exist in model schema.'));
  }

  done();
});

La falla:

19 passing (25ms)
  1 failing

  1) Model Base should throw an error if you try to get an undefined property:
     Error: Property does not exist in model schema.
doremi
fuente

Respuestas:

340

Tienes que pasar una función a expect. Me gusta esto:

expect(model.get.bind(model, 'z')).to.throw('Property does not exist in model schema.');
expect(model.get.bind(model, 'z')).to.throw(new Error('Property does not exist in model schema.'));

La forma en que lo está haciendo, está de paso a expectla consecuencia de llamar model.get('z'). Pero para probar si se arroja algo, debe pasarle una función expect, que expectse llamará a sí misma. El bindmétodo utilizado anteriormente crea una nueva función que cuando se llama llamará model.getcon thisset al valor de modely el primer argumento establecido a 'z'.

Una buena explicación de bindse puede encontrar aquí .

Louis
fuente
Pasé una función, ¿no? modelLa instancia tiene una función llamada get que pasé / invoqué en wait.
doremi
No, mira la explicación que agregué mientras escribías tu comentario.
Louis
47
Oof ¿Por qué los documentos ( chaijs.com/api/bdd/#throw ) no demuestran este uso de bind? Parece que el escenario de prueba más común para to.throwes probar una condición particular dentro de una función, que requiere llamar a esa función con el estado / argumentos inválidos. (Para el caso ... ¿por qué los enlaces profundos de chaijs.com realmente no lo hacen?)
ericsoco
Sin embargo, cuando pasa algunos parámetros que no deberían arrojarse, la prueba sigue siendo un paso.
Alexandros Spyropoulos
66
Tenga en cuenta que esto (a partir de septiembre de 2017) no funcionará para las funciones asíncronas: consulte github.com/chaijs/chai/issues/882#issuecomment-322131680 y la discusión asociada.
ChrisV
175

Como dice esta respuesta , también puede envolver su código en una función anónima como esta:

expect(function(){
    model.get('z');
}).to.throw('Property does not exist in model schema.');
twiz
fuente
77
Esto no funciona para llamadas a funciones asincrónicas. Supongamos que model.get es asíncrono que devuelve la promesa. Sin embargo, arroja un error. Si intento el enfoque anterior, es "Tiempo de espera" ya que tenemos que notificar "hecho" a mocha. Al mismo tiempo, no puedo intentarlo expect(function(){ model.get('z'); }).to.throw('Property does not exist in model schema.').notify(done); ya que no hay un método de notificación.
Anand N
@AnandN Si entiendo tu problema, parece que solo necesitas refactorizar tu código para manejar el error. ¿El error no controlado en la función asíncrona no será un problema en su aplicación real también?
twiz
2
Gracias twiz por tu respuesta. Estamos trabajando en un entorno integrado, el módulo de uso se encarga de detectar las excepciones. Entonces, el problema es cuando intentamos ejecutar casos de prueba unitarios. Finalmente, utilizamos el siguiente enfoque para que funcione catch (err) { expect(err).equal('Error message to be checked'); done(); }
Anand N
1
Buena solución, excepto cuando está utilizando thisdentro de la función que se llamará. Entonces .bindes el camino correcto a seguir.
rabbitco
@AnandN La llamada de función asincrónica no se lanza , rechaza s. Para referencia futura, chai-as-prometió maneja esto muy bien.
user5532169
85

Y si ya está utilizando ES6 / ES2015, también puede utilizar una función de flecha. Básicamente es lo mismo que usar una función anónima normal pero más corta.

expect(() => model.get('z')).to.throw('Property does not exist in model schema.');
Daniel T.
fuente
PUEDE HABER un problema con esto porque las funciones de flecha toman su alcance circundante parathis
Eric Hodonsky
1
@Relic Sí, muy cierto. Esto también puede ser una gran ventaja de las funciones de flecha. Las funciones de flecha 'heredan' thisdel ámbito en el que se crearon. A menudo, esto puede ser una ventaja, ya que evita la necesidad bindde incorporar funciones a su thisobjeto manualmente.
Stijn de Witt
@StijndeWitt esto no es una ventaja o desventaja, es control de alcance e intencional. En realidad, es el azúcar de sintaxis para usar bindy siempre se une al thisalcance principal. Mi intención en el comentario era solo asegurar que los lectores estuvieran al tanto de una posible caída en boxes.
Eric Hodonsky
1
@Relic Sí, estoy de acuerdo contigo. Se puede usar con ventaja y puede ser una buena razón para usar una función de flecha.
Stijn de Witt
75

Esta pregunta tiene muchos, muchos duplicados, incluidas preguntas que no mencionan la biblioteca de afirmaciones Chai. Aquí están los elementos básicos recopilados juntos:

La aserción debe llamar a la función, en lugar de evaluarla inmediatamente.

assert.throws(x.y.z);      
   // FAIL.  x.y.z throws an exception, which immediately exits the
   // enclosing block, so assert.throw() not called.
assert.throws(()=>x.y.z);  
   // assert.throw() is called with a function, which only throws
   // when assert.throw executes the function.
assert.throws(function () { x.y.z });   
   // if you cannot use ES6 at work
function badReference() { x.y.z }; assert.throws(badReference);  
   // for the verbose
assert.throws(()=>model.get(z));  
   // the specific example given.
homegrownAssertThrows(model.get, z);
   //  a style common in Python, but not in JavaScript

Puede verificar errores específicos utilizando cualquier biblioteca de aserciones:

Nodo

  assert.throws(() => x.y.z);
  assert.throws(() => x.y.z, ReferenceError);
  assert.throws(() => x.y.z, ReferenceError, /is not defined/);
  assert.throws(() => x.y.z, /is not defined/);
  assert.doesNotThrow(() => 42);
  assert.throws(() => x.y.z, Error);
  assert.throws(() => model.get.z, /Property does not exist in model schema./)

Debería

  should.throws(() => x.y.z);
  should.throws(() => x.y.z, ReferenceError);
  should.throws(() => x.y.z, ReferenceError, /is not defined/);
  should.throws(() => x.y.z, /is not defined/);
  should.doesNotThrow(() => 42);
  should.throws(() => x.y.z, Error);
  should.throws(() => model.get.z, /Property does not exist in model schema./)

Chai esperar

  expect(() => x.y.z).to.throw();
  expect(() => x.y.z).to.throw(ReferenceError);
  expect(() => x.y.z).to.throw(ReferenceError, /is not defined/);
  expect(() => x.y.z).to.throw(/is not defined/);
  expect(() => 42).not.to.throw();
  expect(() => x.y.z).to.throw(Error);
  expect(() => model.get.z).to.throw(/Property does not exist in model schema./);

Debe manejar las excepciones que 'escapan' de la prueba

it('should handle escaped errors', function () {
  try {
    expect(() => x.y.z).not.to.throw(RangeError);
  } catch (err) {
    expect(err).to.be.a(ReferenceError);
  }
});

Esto puede parecer confuso al principio. Al igual que andar en bicicleta, solo hace clic para siempre una vez que hace clic.

Charles Merriam
fuente
15

ejemplos de doc ...;)

porque confías en el thiscontexto:

  • que se pierde cuando la función es invocada por .throw
  • no hay forma de que sepa qué se supone que es esto

tienes que usar una de estas opciones:

  • ajusta el método o la llamada a la función dentro de otra función
  • atar el contexto

    // wrap the method or function call inside of another function
    expect(function () { cat.meow(); }).to.throw();  // Function expression
    expect(() => cat.meow()).to.throw();             // ES6 arrow function
    
    // bind the context
    expect(cat.meow.bind(cat)).to.throw();           // Bind
Michal Miky Jankovský
fuente
Así es como lo hago yo también. Creo que la implementación de ES6 es, con mucho, la más fácil de leer
relief.melone
1

Otra posible implementación, más engorrosa que la solución .bind (), pero que ayuda a hacer el punto de que esperar () requiere una función que proporcione un thiscontexto a la función cubierta, puede usar un call(), por ejemplo,

expect(function() {model.get.call(model, 'z');}).to.throw('...');

SeanOlson
fuente
0

He encontrado una buena forma de evitarlo:

// The test, BDD style
it ("unsupported site", () => {
    The.function(myFunc)
    .with.arguments({url:"https://www.ebay.com/"})
    .should.throw(/unsupported/);
});


// The function that does the magic: (lang:TypeScript)
export const The = {
    'function': (func:Function) => ({
        'with': ({
            'arguments': function (...args:any) {
                return () => func(...args);
            }
        })
    })
};

Es mucho más legible que mi versión anterior:

it ("unsupported site", () => {
    const args = {url:"https://www.ebay.com/"}; //Arrange
    function check_unsupported_site() { myFunc(args) } //Act
    check_unsupported_site.should.throw(/unsupported/) //Assert
});
Dani-Br
fuente