Wie schreibe ich einen Test, der erwartet, dass ein Fehler in Jasmine ausgelöst wird?

490

Ich versuche, einen Test für das Jasmine Test Framework zu schreiben, der einen Fehler erwartet. Im Moment verwende ich eine Jasmine Node.js-Integration von GitHub .

In meinem Knotenmodul habe ich folgenden Code:

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

Jetzt versuche ich einen Test zu schreiben, der diesen Fehler erwartet:

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

Ich habe auch Error()und einige andere Varianten ausprobiert und kann einfach nicht herausfinden, wie es funktioniert.

Echox
quelle
4
Um Argumente an die zu testende Funktion zu übergeben, ohne eine anonyme Funktion zu verwenden, versuchen Sie Function.bind: stackoverflow.com/a/13233194/294855
Danyal Aytekin

Antworten:

802

Sie sollten eine Funktion an den expect(...)Aufruf übergeben. Der Code, den Sie hier haben:

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

versucht tatsächlich , parser.parse(raw) einen Versuch aufzurufen , das Ergebnis an weiterzugeben expect(...),

Versuchen Sie stattdessen eine anonyme Funktion:

expect( function(){ parser.parse(raw); } ).toThrow(new Error("Parsing is not possible"));
Pete Hodgson
quelle
28
Wenn Sie auch keine Argumente übergeben müssen, können Sie auch einfach die zu erwartende Funktion übergeben:expect(parser.parse).toThrow(...)
SubmittedDenied
60
Hilfreicher Tipp: Sie können einfach anrufen expect(blah).toThrow(). Keine Argumente bedeuten überprüfen, ob es überhaupt wirft. Kein String-Matching erforderlich. Siehe auch: stackoverflow.com/a/9525172/1804678
Jess
1
Meiner Meinung nach ist die Absicht des Tests beim Einwickeln in eine anonyme Funktion offensichtlicher. Außerdem bleibt es bei allen Tests konsistent, wenn Sie beispielsweise Parameter an die Zielfunktion übergeben müssen, damit diese ausgelöst wird.
Beez
10
@SubmittedDenied: Das funktioniert im Allgemeinen nicht! Wenn es parser.parseverwendet wird this, führt das Übergeben ohne Kontext zu unerwarteten Ergebnissen. Sie könnten bestehen parser.parse.bind(parser), aber ehrlich gesagt ... eine anonyme Funktion wäre eleganter.
Mhelvens
2
@LanceKind Entschuldigung an Necro, aber der Grund, warum Sie eine Funktion übergeben müssen, ist, dass ein Wert ausgewertet wird und eine Ausnahme auslöst, bevor er überhaupt in die Erwartung übergeben wurde.
1gLassitude
68

Du benutzt:

expect(fn).toThrow(e)

Aber wenn Sie sich den Funktionskommentar ansehen (erwartet wird ein String):

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

Ich nehme an, Sie sollten es wahrscheinlich so schreiben (mit Lambda - anonyme Funktion):

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

Dies wird im folgenden Beispiel bestätigt:

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

Douglas Crockford empfiehlt diesen Ansatz nachdrücklich, anstatt "throw new Error ()" (Prototyping-Methode) zu verwenden:

throw {
   name: "Error",
   message: "Parsing is not possible"
}
Andrzej Śliwa
quelle
3
Wenn Sie sich den Code toThrow ansehen, wird entweder ein Ausnahmeobjekt / oder / eine Zeichenfolge verwendet. Schauen Sie sich zum Beispiel die Anrufe an, die an expected.message getätigt werden.
Pete Hodgson
1
Es
scheint
1
Vielen Dank, das hat funktioniert. Ich nahm noch die Antwort von Pete, beacuse seine Antwort es klarer zu mir gemacht, dass ich habe ein Lambda zu verwenden. Immer noch +1 :-) Danke!
Echox
16
Wenn Sie ein Objekt anstelle eines Fehlers auslösen (wie in Ihrem Beispiel unten), wird in den Browsern, die dies unterstützen, keine Stapelverfolgung angezeigt.
Kybernetikos
2
@kybernetikos überraschenderweise nicht ganz wahr; In der Chrome-Konsole wird weiterhin eine Stapelverfolgung gedruckt, wenn Sie eine Nicht- Error( jsfiddle.net/k1mxey8j ) auslösen . Ihr geworfenes Objekt verfügt jedoch natürlich nicht über die .stackEigenschaft. Dies kann wichtig sein, wenn Sie eine automatische Fehlerberichterstattung einrichten möchten .
Mark Amery
24

Eine elegantere Lösung als das Erstellen einer anonymen Funktion, deren einziger Zweck darin besteht, eine andere zu verpacken, ist die Verwendung der bindFunktion von es5 . Die Bindungsfunktion erstellt eine neue Funktion, deren thisSchlüsselwort beim Aufruf auf den angegebenen Wert gesetzt wird, wobei eine bestimmte Folge von Argumenten vor jeder angegebenen steht, die beim Aufruf der neuen Funktion angegeben wird.

Anstatt:

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

Erwägen:

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

Die Bindungssyntax ermöglicht es Ihnen, Funktionen mit unterschiedlichen thisWerten zu testen und macht den Test meiner Meinung nach besser lesbar. Siehe auch: https://stackoverflow.com/a/13233194/1248889

Jonathan Gawrych
quelle
23

Ich ersetze Jasmines toThrow-Matcher durch den folgenden, mit dem Sie die Namenseigenschaft oder die Nachrichteneigenschaft der Ausnahme abgleichen können. Für mich macht dies das Schreiben von Tests einfacher und weniger spröde, da ich Folgendes tun kann:

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

und dann mit folgendem testen:

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

Auf diese Weise kann ich die Ausnahmemeldung später optimieren, ohne die Tests zu unterbrechen, wenn es darauf ankommt, dass der erwartete Ausnahmetyp ausgelöst wird.

Dies ist der Ersatz für toThrow, der dies ermöglicht:

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
quelle
4
Ein netter Ansatz, aber ist {name: '...', message: '...'} ein richtiges Fehlerobjekt in JavaScript?
Marc
1
Netter Kommentar @Marc. Sie haben Recht, die Eigenschaft name ist nicht Standard. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… , aber ist das so falsch?
Jess
4
@ Jake! Ich habe einen besseren Weg gefunden !!!! Sie können einfach anrufen expect(blah).toThrow(). Keine Argumente bedeuten überprüfen, ob es überhaupt wirft. Kein String-Matching erforderlich. Siehe auch: stackoverflow.com/a/9525172/1804678
Jess
5
Danke Jess - das stimmt, aber dann könnte es einen anderen Fehler geben, wie einen TypeError, und mein Test wird falsch bestanden und einen echten Fehler maskiert.
Jake
4
Sie können jetzt auch eine RegEx als Argument für toThrow () verwenden.
Tony O'Hagan
21

Wie bereits erwähnt, muss eine Funktion übergeben werden, toThrowda es sich um die Funktion handelt, die Sie in Ihrem Test beschreiben: "Ich erwarte, dass diese Funktion x auslöst."

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

Wenn Sie Jasmine-Matcher verwenden , können Sie auch eine der folgenden Optionen verwenden, wenn sie der Situation entsprechen.

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

oder

// 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
quelle
3
Es ist expect(foo).toThrowError(TypeError);in Jasmine 2.5: jasmine.github.io/2.5/introduction
Benny Neugebauer
9

Ich weiß, dass das mehr Code ist, aber Sie können auch Folgendes tun:

try
   do something
   @fail Error("should send a Exception")
 catch e
   expect(e.name).toBe "BLA_ERROR"
   expect(e.message).toBe 'Message'
Tolbard
quelle
Ich neige dazu, den Aspekt der "
Selbstdokumentation"
6

Für Coffeescript-Liebhaber

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

Für alle, die immer noch mit diesem Problem konfrontiert sind, hat die veröffentlichte Lösung nicht funktioniert und diesen Fehler immer wieder ausgelöst: Error: Expected function to throw an exception. Später wurde mir klar, dass die Funktion, von der ich erwartet hatte, dass sie einen Fehler auslöst, eine asynchrone Funktion ist und ein Versprechen erwartet abgelehnt werden und dann Fehler werfen und das habe ich in meinem Code getan:

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

und das habe ich in meinem Test gemacht und es hat funktioniert:

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
quelle
Danke dafür. Ich war sehr verwirrt, aber Ihr Kommentar macht vollkommen Sinn. Ich habe das Problem mit dem neuen expectAsync jasmine.github.io/api/3.3/async-matchers.html
Benjamin