¿Cómo escribir una prueba que espera que se arroje un error en Jasmine?

490

Estoy tratando de escribir una prueba para Jasmine Test Framework que espera un error. En este momento estoy usando una integración Jasmine Node.js de GitHub .

En mi módulo Node tengo el siguiente código:

throw new Error("Parsing is not possible");

Ahora trato de escribir una prueba que espera este error:

describe('my suite...', function() {
    [..]
    it('should not parse foo', function() {
    [..]
        expect(parser.parse(raw)).toThrow(new Error("Parsing is not possible"));
    });
});

También probé Error()y algunas otras variantes y simplemente no puedo entender cómo hacerlo funcionar.

echox
fuente
44
Para pasar argumentos a la función que se está probando, sin usar una función anónima, intente Function.bind: stackoverflow.com/a/13233194/294855
Danyal Aytekin

Respuestas:

802

deberías pasar una función a la expect(...)llamada. El código que tienes aquí:

// incorrect:
expect(parser.parse(raw)).toThrow(new Error("Parsing is not possible"));

está intentando llamar realmente parser.parse(raw)en un intento de pasar el resultado a expect(...),

Intente usar una función anónima en su lugar:

expect( function(){ parser.parse(raw); } ).toThrow(new Error("Parsing is not possible"));
Pete Hodgson
fuente
28
Si no necesita pasar argumentos también, también puede pasar la función para esperar:expect(parser.parse).toThrow(...)
Enviado el
6060
Consejo útil: simplemente puede llamar expect(blah).toThrow(). Sin argumentos significa comprobar para ver si arroja algo. No se requiere coincidencia de cadenas. Ver también: stackoverflow.com/a/9525172/1804678
Jess
1
En mi opinión, es más obvio en cuanto a la intención de la prueba cuando se envuelve en una función anónima. Además, permanece constante entre todas las pruebas cuando, por ejemplo, tiene que pasar parámetros a la función de destino para que se lance.
Beez
10
@SubmittedDenied: ¡Esto no funciona en general! Si se parser.parseusa this, pasarlo sin contexto producirá resultados inesperados. Podrías pasar parser.parse.bind(parser), pero honestamente ... una función anónima sería más elegante.
mhelvens
2
@LanceKind lamento necro, pero, la razón por la que tiene que pasar una función es que un valor sería evaluado y arrojaría una excepción incluso antes de pasarlo a la expectativa.
1gLassitude
68

Tu estas usando:

expect(fn).toThrow(e)

Pero si observa el comentario de la función (se espera que sea una cadena):

294 /**
295  * Matcher that checks that the expected exception was thrown by the actual.
296  *
297  * @param {String} expected
298  */
299 jasmine.Matchers.prototype.toThrow = function(expected) {

Supongo que probablemente deberías escribirlo así (usando lambda - función anónima):

expect(function() { parser.parse(raw); } ).toThrow("Parsing is not possible");

Esto se confirma en el siguiente ejemplo:

expect(function () {throw new Error("Parsing is not possible")}).toThrow("Parsing is not possible");

Douglas Crockford recomienda encarecidamente este enfoque, en lugar de usar "throw new Error ()" (forma de creación de prototipos):

throw {
   name: "Error",
   message: "Parsing is not possible"
}
Andrzej Śliwa
fuente
3
En realidad, mirar el código toThrow tomará felizmente un objeto de excepción / o / una cadena. Echa un vistazo a las llamadas que está haciendo al esperado.mensaje, por ejemplo.
Pete Hodgson
1
Parece permitir que la cadena sea un efecto secundario de la cadena que no tiene propiedad de mensaje
mpapis
1
Muchas gracias que funcionó. Todavía acepté la respuesta de Pete, porque su respuesta me dejó más claro, que tengo que usar una lambda. Todavía +1 :-) ¡Gracias!
echox
16
Si arroja un objeto en lugar de un Error (como en su ejemplo en la parte inferior), no obtendrá un seguimiento de la pila en los navegadores que lo admiten.
kybernetikos
2
@kybernetikos sorprendentemente, no del todo cierto; aún obtendrá un seguimiento de la pila impreso en la consola de Chrome si arroja un archivo que no sea Error( jsfiddle.net/k1mxey8j ). Sin embargo, su objeto lanzado, por supuesto, no tendrá la .stackpropiedad, lo que puede ser importante si desea configurar informes automáticos de errores.
Mark Amery
24

Una solución más elegante que crear una función anónima cuyo único propósito es envolver otra es usar la bindfunción es5 . La función de vinculación crea una nueva función que, cuando se llama, tiene su thispalabra clave establecida en el valor proporcionado, con una secuencia dada de argumentos que precede a cualquier proporcionada cuando se llama a la nueva función.

En vez de:

expect(function () { parser.parse(raw, config); } ).toThrow("Parsing is not possible");

Considerar:

expect(parser.parse.bind(parser, raw, config)).toThrow("Parsing is not possible");

La sintaxis de enlace le permite probar funciones con diferentes thisvalores y, en mi opinión, hace que la prueba sea más legible. Ver también: https://stackoverflow.com/a/13233194/1248889

Jonathan Gawrych
fuente
23

Reemplazo el emparejador toThrow de Jasmine con lo siguiente, que le permite coincidir con la propiedad del nombre de la excepción o su propiedad de mensaje. Para mí, esto hace que las pruebas sean más fáciles de escribir y menos frágiles, ya que puedo hacer lo siguiente:

throw {
   name: "NoActionProvided",
   message: "Please specify an 'action' property when configuring the action map."
}

y luego prueba con lo siguiente:

expect (function () {
   .. do something
}).toThrow ("NoActionProvided");

Esto me permite ajustar el mensaje de excepción más tarde sin romper las pruebas, cuando lo importante es que arrojó el tipo de excepción esperado.

Este es el reemplazo de toThrow que permite esto:

jasmine.Matchers.prototype.toThrow = function(expected) {
  var result = false;
  var exception;
  if (typeof this.actual != 'function') {
    throw new Error('Actual is not a function');
  }
  try {
    this.actual();
  } catch (e) {
    exception = e;
  }
  if (exception) {
      result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected) || this.env.equals_(exception.name, expected));
  }

  var not = this.isNot ? "not " : "";

  this.message = function() {
    if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
      return ["Expected function " + not + "to throw", expected ? expected.name || expected.message || expected : " an exception", ", but it threw", exception.name || exception.message || exception].join(' ');
    } else {
      return "Expected function to throw an exception.";
    }
  };

  return result;
};
Jake
fuente
44
Un buen enfoque, pero ¿es {nombre: '...', mensaje: '...'} un objeto de error adecuado en JavaScript?
Marc
1
Buen comentario @Marc. Tienes razón, la propiedad de nombre no es estándar. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… , pero ¿está tan mal?
Jess
44
@Jake! ¡Encontré una mejor manera! Simplemente puedes llamar expect(blah).toThrow(). Sin argumentos significa comprobar para ver si arroja algo. No se requiere coincidencia de cadenas. Ver también: stackoverflow.com/a/9525172/1804678
Jess
55
Gracias Jess, eso es cierto, pero entonces podría estar arrojando algún otro error, como un TypeError, y mi prueba pasará incorrectamente, enmascarando un error real.
Jake
44
Ahora también puede usar un RegEx como argumento para toThrow ().
Tony O'Hagan
21

Como se mencionó anteriormente, es necesario pasar una función, toThrowya que es la función que está describiendo en su prueba: "Espero que esta función arroje x"

expect(() => parser.parse(raw))
  .toThrow(new Error('Parsing is not possible'));

Si usa Jasmine-Matchers , también puede usar uno de los siguientes cuando se adapten a la situación;

// I just want to know that an error was
// thrown and nothing more about it
expect(() => parser.parse(raw))
  .toThrowAnyError();

o

// I just want to know that an error of 
// a given type was thrown and nothing more
expect(() => parser.parse(raw))
  .toThrowErrorOfType(TypeError);
Jamie Mason
fuente
3
Está expect(foo).toThrowError(TypeError);en Jasmine 2.5: jasmine.github.io/2.5/introduction
Benny Neugebauer
9

Sé que es más código pero también puedes hacer:

try
   do something
   @fail Error("should send a Exception")
 catch e
   expect(e.name).toBe "BLA_ERROR"
   expect(e.message).toBe 'Message'
tolbard
fuente
Me gusta el aspecto de 'autodocumentación' en esto ... hace que sea muy evidente que la unidad está probando un estado de error
JRulle
6

Para los amantes del café

expect( => someMethodCall(arg1, arg2)).toThrow()
fernandohur
fuente
3

Para cualquiera que todavía pueda estar enfrentando este problema, para mí la solución publicada no funcionó y siguió arrojando este error: Error: Expected function to throw an exception. más tarde me di cuenta de que la función que esperaba lanzar un error era una función asincrónica y esperaba prometer ser rechazado y luego arrojar error y eso es lo que estaba haciendo en mi código:

throw new Error('REQUEST ID NOT FOUND');

y eso fue lo que hice en mi prueba y funcionó:

it('Test should throw error if request not found', willResolve(() => {
         const promise = service.getRequestStatus('request-id');
                return expectToReject(promise).then((err) => {
                    expect(err.message).toEqual('REQUEST NOT FOUND');
                });
            }));
arifaBatool
fuente
Gracias por esto. Estaba muy confundido, pero tu comentario tiene mucho sentido. Solucioné el problema usando el nuevo expectAsync jasmine.github.io/api/3.3/async-matchers.html
Benjamin