Come scrivere un test che prevede che venga lanciato un errore in Jasmine?


490

Sto cercando di scrivere un test per il Jasmine Test Framework che prevede un errore. Al momento sto usando un'integrazione Jasmine Node.js da GitHub .

Nel mio modulo Node ho il seguente codice:

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

Ora provo a scrivere un test che prevede questo errore:

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

Ho provato anche Error()alcune altre varianti e non riesco proprio a capire come farlo funzionare.


4
Per passare argomenti alla funzione testata, senza utilizzare una funzione anonima, provare Function.bind: stackoverflow.com/a/13233194/294855
Danyal Aytekin,

Risposte:


802

dovresti passare una funzione nella expect(...)chiamata. Il codice che hai qui:

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

sta cercando di chiamare effettivamente parser.parse(raw)nel tentativo di passare il risultato in expect(...),

Prova invece a utilizzare una funzione anonima:

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

28
Se non hai bisogno di passare anche argomenti, puoi semplicemente passare la funzione per aspettarti:expect(parser.parse).toThrow(...)
SubmittedDenied

60
Suggerimento utile: puoi semplicemente chiamare expect(blah).toThrow(). Nessun argomento significa controllare per vedere che getta affatto. Nessuna corrispondenza di stringa richiesta. Vedi anche: stackoverflow.com/a/9525172/1804678
Jess

1
A mio avviso, è più evidente l'intento del test quando si avvolge in una funzione anonima. Inoltre, rimane coerente tra tutti i test quando, ad esempio, è necessario passare i parametri alla funzione target per farlo lanciare.
Beez,

10
@SubmittedDenied: questo non funziona in generale! Se viene parser.parseutilizzato this, passarlo senza contesto produrrà risultati imprevisti. Potresti passare parser.parse.bind(parser), ma onestamente ... una funzione anonima sarebbe più elegante.
mhelvens,

2
@LanceKind mi dispiace per necro, ma, il motivo per cui devi passare una funzione è che un valore verrebbe valutato e genererebbe un'eccezione prima ancora che fosse passato nell'aspettativa.
gLassitude

68

Tu stai usando:

expect(fn).toThrow(e)

Ma se darai un'occhiata al commento della funzione (previsto è stringa):

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) {

Suppongo che dovresti probabilmente scriverlo in questo modo (usando lambda - funzione anonima):

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

Ciò è confermato nel seguente esempio:

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

Douglas Crockford raccomanda vivamente questo approccio, invece di usare "throw new Error ()" (prototipazione):

throw {
   name: "Error",
   message: "Parsing is not possible"
}

3
In realtà, guardando il codice toThrow prenderà felicemente un oggetto di eccezione / o / una stringa. Ad esempio, controlla le chiamate che sta effettuando per il messaggio previsto.
Pete Hodgson,

1
Si giunge a consentire la stringa come effetto collaterale della stringa senza proprietà del messaggio
mpapis,

1
Grazie mille che ha funzionato. Ho ancora accettato la risposta di Pete, Allucinante la sua risposta reso più chiaro a me, che ho devo usare un lambda. Ancora +1 :-) Grazie!
eco

16
Se si lancia un oggetto anziché un errore (come nell'esempio in basso), non si otterrà una traccia dello stack nei browser che lo supportano.
kybernetikos

2
@kybernetikos sorprendentemente, non del tutto vero; otterrai comunque una traccia dello stack stampata nella console di Chrome se lanci un non- Error( jsfiddle.net/k1mxey8j ). Tuttavia, l'oggetto lanciato naturalmente non avrà la .stackproprietà, il che può essere importante se si desidera impostare la segnalazione automatica degli errori.
Mark Amery,

24

Una soluzione più elegante della creazione di una funzione anonima che ha come unico scopo quello di avvolgerne un'altra, è quella di utilizzare la bindfunzione es5 . La funzione bind crea una nuova funzione che, quando viene chiamata, ha la thisparola chiave impostata sul valore fornito, con una determinata sequenza di argomenti che precede qualsiasi fornita quando viene chiamata la nuova funzione.

Invece di:

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

Prendere in considerazione:

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

La sintassi del bind consente di testare funzioni con thisvalori diversi e, a mio avviso, rende il test più leggibile. Vedi anche: https://stackoverflow.com/a/13233194/1248889


23

Sostituisco il matcher toThrow di Jasmine con il seguente, che ti consente di abbinare la proprietà name dell'eccezione o la proprietà message. Per me questo rende i test più facili da scrivere e meno fragili, poiché posso fare quanto segue:

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

e quindi prova con quanto segue:

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

Questo mi consente di modificare il messaggio di eccezione in un secondo momento senza interrompere i test, quando l'importante è che abbia generato il tipo di eccezione previsto.

Questa è la sostituzione di toThrow che consente questo:

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

4
Un buon approccio ma {name: '...', message: '...'} un oggetto Error corretto in JavaScript?
Marc

1
Bel commento @Marc. Hai ragione, la proprietà name non è standard. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… , ma è così sbagliato?
Jess,

4
@Jake! Ho trovato un modo migliore !!!! Puoi semplicemente chiamare expect(blah).toThrow(). Nessun argomento significa controllare per vedere che getta affatto. Nessuna corrispondenza di stringa richiesta. Vedi anche: stackoverflow.com/a/9525172/1804678
Jess,

5
Grazie Jess - è vero, ma potrebbe essere un altro errore, come un TypeError, e il mio test passerà in modo errato, mascherando un vero bug.
Jake,

4
Ora puoi anche usare un RegEx come argomento per toThrow ().
Tony O'Hagan,

21

Come accennato in precedenza, una funzione deve essere passata in toThrowquanto è la funzione che stai descrivendo nel test: "Mi aspetto che questa funzione passi x"

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

Se usi Jasmine-Matchers puoi anche usare uno dei seguenti quando si adattano alla situazione;

// 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);

3
È expect(foo).toThrowError(TypeError);in Jasmine 2.5: jasmine.github.io/2.5/introduction
Benny Neugebauer,

9

So che è più codice ma puoi anche fare:

try
   do something
   @fail Error("should send a Exception")
 catch e
   expect(e.name).toBe "BLA_ERROR"
   expect(e.message).toBe 'Message'

Tendo ad apprezzare l'aspetto di "autocertificazione" di questo ... rende molto evidente che stai testando un'unità di stato di errore
JRulle

6

Per gli amanti del caffè

expect( => someMethodCall(arg1, arg2)).toThrow()

3

Per chiunque possa ancora affrontare questo problema, per me la soluzione pubblicata non ha funzionato e ha continuato a lanciare questo errore: in Error: Expected function to throw an exception. seguito ho capito che la funzione che mi aspettavo di lanciare un errore era una funzione asincrona e mi aspettavo la promessa di essere rifiutato e quindi gettare errore ed è quello che stavo facendo nel mio codice:

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

ed è quello che ho fatto nel mio test e ha funzionato:

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

Grazie per questo. Ero molto confuso, ma il tuo commento ha perfettamente senso. Ho risolto il problema utilizzando il nuovo expectAsync jasmine.github.io/api/3.3/async-matchers.html
Benjamin
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.