Comment écrire un test qui s'attend à ce qu'une erreur soit lancée dans Jasmine?

490

J'essaie d'écrire un test pour Jasmine Test Framework qui attend une erreur. Pour le moment, j'utilise une intégration Jasmine Node.js de GitHub .

Dans mon module Node, j'ai le code suivant:

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

Maintenant, j'essaie d'écrire un test qui attend cette erreur:

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

J'ai également essayé Error()et quelques autres variantes et je n'arrive pas à comprendre comment le faire fonctionner.

écho
la source
4
Pour passer des arguments à la fonction en cours de test, sans utiliser de fonction anonyme, essayez Function.bind: stackoverflow.com/a/13233194/294855
Danyal Aytekin

Réponses:

802

vous devez transmettre une fonction à l' expect(...)appel. Le code que vous avez ici:

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

essaie d' appeler parser.parse(raw) pour tenter de transmettre le résultat expect(...),

Essayez plutôt d'utiliser une fonction anonyme:

expect( function(){ parser.parse(raw); } ).toThrow(new Error("Parsing is not possible"));
Pete Hodgson
la source
28
Si vous n'avez pas non plus besoin de passer d'arguments, vous pouvez également passer la fonction attendue:expect(parser.parse).toThrow(...)
SubmittedDenied
60
Conseil utile: vous pouvez simplement appeler expect(blah).toThrow(). Aucun argument signifie vérifier pour voir qu'il jette du tout. Aucune correspondance de chaîne n'est requise. Voir aussi: stackoverflow.com/a/9525172/1804678
Jess
1
À mon avis, il est plus évident quant à l'intention du test lors de l'encapsulation dans une fonction anonyme. En outre, il reste cohérent parmi tous les tests lorsque, par exemple, vous devez passer des paramètres à la fonction cible pour la faire lancer.
Beez
10
@SubmittedDenied: Cela ne fonctionne pas en général! S'il est parser.parseutilisé this, le passer sans contexte produira des résultats inattendus. Vous pourriez passer parser.parse.bind(parser), mais honnêtement ... une fonction anonyme serait plus élégante.
mhelvens
2
@LanceKind désolé pour necro, mais la raison pour laquelle vous devez passer une fonction est qu'une valeur serait évaluée et lèverait une exception avant même qu'elle ne soit passée dans l'attente.
1gLassitude
68

Vous utilisez:

expect(fn).toThrow(e)

Mais si vous regardez le commentaire de la fonction (la chaîne attendue est):

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

Je suppose que vous devriez probablement l'écrire comme ceci (en utilisant lambda - fonction anonyme):

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

Ceci est confirmé dans l'exemple suivant:

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

Douglas Crockford recommande fortement cette approche, au lieu d'utiliser "throw new Error ()" (façon de prototypage):

throw {
   name: "Error",
   message: "Parsing is not possible"
}
Andrzej Śliwa
la source
3
En fait, regarder le code toThrow prendra volontiers un objet d'exception / ou / une chaîne. Découvrez par exemple les appels qu'il fait vers le message attendu.
Pete Hodgson
1
Il sert à autoriser la chaîne comme effet secondaire d'une chaîne n'ayant aucune propriété de message
mpapis
1
Merci beaucoup qui a fonctionné. J'ai quand même accepté la réponse de Pete, car sa réponse m'a fait plus clairement comprendre que je dois utiliser un lambda. Encore +1 :-) Merci!
echox
16
Si vous lancez un objet plutôt qu'une erreur (comme dans votre exemple en bas), vous n'aurez pas de trace de pile dans les navigateurs qui le prennent en charge.
kybernetikos
2
@kybernetikos étonnamment, pas entièrement vrai; vous obtiendrez toujours une trace de pile imprimée dans la console Chrome si vous lancez un non Error( jsfiddle.net/k1mxey8j ). Cependant, votre objet jeté n'aura bien sûr pas la .stackpropriété, ce qui peut être important si vous souhaitez configurer un rapport d'erreurs automatisé .
Mark Amery
24

Une solution plus élégante que de créer une fonction anonyme dont le seul but est d'en envelopper une autre est d'utiliser la bindfonction d'es5 . La fonction de liaison crée une nouvelle fonction qui, lorsqu'elle est appelée, a son thismot clé défini sur la valeur fournie, avec une séquence donnée d'arguments précédant tout argument fourni lorsque la nouvelle fonction est appelée.

Au lieu de:

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

Considérer:

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

La syntaxe de liaison vous permet de tester des fonctions avec des thisvaleurs différentes et, à mon avis, rend le test plus lisible. Voir aussi: https://stackoverflow.com/a/13233194/1248889

Jonathan Gawrych
la source
23

Je remplace le match toThrow de Jasmine par ce qui suit, ce qui vous permet de faire correspondre la propriété de nom de l'exception ou sa propriété de message. Pour moi, cela rend les tests plus faciles à écrire et moins fragiles, car je peux faire ce qui suit:

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

puis testez avec les éléments suivants:

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

Cela me permet de modifier le message d'exception plus tard sans casser les tests, lorsque l'important est qu'il a levé le type d'exception attendu.

C'est le remplacement de toThrow qui permet ceci:

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
la source
4
Une bonne approche mais est {nom: '...', message: '...'} un objet d'erreur approprié en JavaScript?
Marc
1
Joli commentaire @Marc. Vous avez raison, la propriété du nom n'est pas standard. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… , mais est-ce si mal?
Jess
4
@Jake! J'ai trouvé un meilleur moyen !!!! Vous pouvez simplement appeler expect(blah).toThrow(). Aucun argument signifie vérifier pour voir qu'il jette du tout. Aucune correspondance de chaîne n'est requise. Voir aussi: stackoverflow.com/a/9525172/1804678
Jess
5
Merci Jess - c'est vrai, mais alors il pourrait y avoir une autre erreur, comme une TypeError, et mon test passera incorrectement, masquant un vrai bug.
Jake
4
Vous pouvez également maintenant utiliser un RegEx comme argument pour toThrow ().
Tony O'Hagan
21

Comme mentionné précédemment, une fonction doit être transmise toThrowcar c'est la fonction que vous décrivez dans votre test: "Je m'attends à ce que cette fonction renvoie x"

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

Si vous utilisez Jasmine-Matchers, vous pouvez également utiliser l'un des éléments suivants lorsqu'ils conviennent à la situation;

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

ou

// 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
la source
3
C'est expect(foo).toThrowError(TypeError);dans Jasmine 2.5: jasmine.github.io/2.5/introduction
Benny Neugebauer
9

Je sais que c'est plus de code mais vous pouvez également faire:

try
   do something
   @fail Error("should send a Exception")
 catch e
   expect(e.name).toBe "BLA_ERROR"
   expect(e.message).toBe 'Message'
tolbard
la source
J'ai tendance à aimer l'aspect 'auto-documenté' de cela ... il est très évident que vous testez un état d'erreur
unitaire
6

Pour les amateurs de coffeescript

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

Pour tous ceux qui pourraient encore être confrontés à ce problème, pour moi, la solution publiée ne fonctionnait pas et elle continuait de générer cette erreur: Error: Expected function to throw an exception. j'ai réalisé plus tard que la fonction que je m'attendais à lancer une erreur était une fonction asynchrone et attendait une promesse de être rejeté puis lancer une erreur et c'est ce que je faisais dans mon code:

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

et c'est ce que j'ai fait dans mon test et cela a fonctionné:

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
la source
Merci pour cela. J'étais très confus, mais votre commentaire est parfaitement logique. J'ai résolu le problème en utilisant le nouveau expectAsync jasmine.github.io/api/3.3/async-matchers.html
Benjamin