Comment tester le type d'exception levée dans Jest

161

Je travaille avec du code dans lequel je dois tester le type d'exception lancée par la fonction (Is it TypeError, ReferenceError etc.).

Mon cadre de test actuel est AVA et je peux le tester en tant que deuxième t.throwsméthode d' argument , comme ici:

it('should throw Error with message \'UNKNOWN ERROR\' when no params were passed', (t) => {
  const error = t.throws(() => {
    throwError();
  }, TypeError);

  t.is(error.message, 'UNKNOWN ERROR');
});

J'ai commencé à réécrire mes tests sur Jest et je n'ai pas trouvé comment le faire facilement. Est-ce même possible?

Tranotheron
la source

Réponses:

225

Dans Jest, vous devez passer une fonction dans expect (function) .toThrow (vide ou type d'erreur).

Exemple:

test("Test description", () => {
  const t = () => {
    throw new TypeError();
  };
  expect(t).toThrow(TypeError);
});

Si vous avez besoin de tester une fonction existante si elle se lance avec un ensemble d'arguments, vous devez l'envelopper dans une fonction anonyme dans expect ().

Exemple:

test("Test description", () => {
  expect(() => {http.get(yourUrl, yourCallbackFn)}).toThrow(TypeError);
});
PeterDanis
la source
79

Un peu bizarre, mais fonctionne et à mon humble avis est bien lisible:

it('should throw Error with message \'UNKNOWN ERROR\' when no params were passed', () => {
  try {
      throwError();
      // Fail test if above expression doesn't throw anything.
      expect(true).toBe(false);
  } catch (e) {
      expect(e.message).toBe("UNKNOWN ERROR");
  }
});

Catchblock attrape votre exception, vous pouvez alors tester sur votre relevé Error. Strange expect(true).toBe(false);est nécessaire pour échouer votre test si prévu Errorne sera pas jeté. Sinon, cette ligne n'est jamais accessible ( Errordoit être levée avant eux).

EDIT: @Kenny Body suggère une meilleure solution qui améliore la qualité du code si vous utilisez expect.assertions()

it('should throw Error with message \'UNKNOWN ERROR\' when no params were passed', () => {
  expect.assertions(1);
  try {
      throwError();
  } catch (e) {
      expect(e.message).toBe("UNKNOWN ERROR");
  }
});

Voir la réponse d'origine avec plus d'explications: Comment tester le type d'exception levée dans Jest

Bodolsog
la source
18
C'est une façon très détaillée de tester les exceptions lorsque Jest a déjà la méthode expect.toThrow () de vérifier les exceptions: jestjs.io/docs/en/expect.html#tothrowerror
gomisha
4
Oui, mais il teste uniquement le type, pas le message ou tout autre contenu et la question portait sur le message de test, pas sur le type.
bodolsog
2
Hah. Vraiment comme celui-ci car mon code doit tester une valeur de l'erreur générée, j'ai donc besoin de l'instance. J'écrirais l'attente erronée comme expect('here').not.toBe('here');juste pour le plaisir :-)
Valery
4
@Valery ou: expect('to be').not.toBe('to be')dans le style Shakespeare.
Michiel van der Blonk
2
réponse la plus sous-estimée!
Edwin Ikechukwu Okonkwo
42

J'utilise une version un peu plus concise:

expect(() => {
  //code block that should throw error
}).toThrow(TypeError) //or .toThrow('expectedErrorMessage')
Tal Joffe
la source
2
Court et précis.
flapjack
33

D'après mon exposition (bien que limitée) à Jest, j'ai trouvé que cela expect().toThrow()convient si vous voulez UNIQUEMENT tester une erreur d'un type spécifique:

expect(() => functionUnderTest()).toThrow(TypeError);

OU une erreur est générée avec un message spécifique:

expect(() => functionUnderTest()).toThrow('Something bad happened!');

Si vous essayez de faire les deux, vous obtiendrez un faux positif. Par exemple, si votre code lance RangeError('Something bad happened!'), ce test passera:

expect(() => functionUnderTest()).toThrow(new TypeError('Something bad happened!'));

La réponse de bodolsog qui suggère d'utiliser un try / catch est proche, mais plutôt que de s'attendre à ce que true soit faux pour s'assurer que les assertions attendues dans la capture sont atteintes, vous pouvez à la place utiliser expect.assertions(2)au début de votre test où 2est le nombre d'assertions attendues . Je pense que cela décrit plus précisément l'intention du test.

Exemple complet de test du type AND message d'une erreur:

describe('functionUnderTest', () => {
    it('should throw a specific type of error.', () => {
        expect.assertions(2);

        try {
            functionUnderTest();
        } catch (error) {
            expect(error).toBeInstanceOf(TypeError);
            expect(error).toHaveProperty('message', 'Something bad happened!');
        }
    }); 
});

Si functionUnderTest()ne lance PAS d'erreur, les assertions seront frappées mais le expect.assertions(2)échouera et le test échouera.

Corps de Kenny
la source
D'oh. J'oublie toujours la fonctionnalité d'attentes multiples d'assertions de Jest (peut-être que je ne la trouve pas personnellement la plus intuitive, mais cela fonctionne certainement dans de tels cas!)
kpollock le
Cela a parfaitement fonctionné pour moi. Cela devrait être utilisé.
Ankit Tanna
expect.hasAssertions()pourrait être une meilleure alternative lorsque le test n'a pas d'assertions à l'extérieur catch, car vous n'avez pas à mettre à jour le nombre si vous ajoutez / supprimez des assertions.
André Sassi
12

Je n'ai pas essayé moi-même mais je suggérerais d'utiliser l' assertion toThrow de Jest . Donc, je suppose que votre exemple ressemblerait à ceci:

it('should throw Error with message \'UNKNOWN ERROR\' when no params were passed', (t) => {
  const error = t.throws(() => {
    throwError();
  }, TypeError);

  expect(t).toThrowError('UNKNOWN ERROR');
  //or
  expect(t).toThrowError(TypeError);
});

Encore une fois, je ne l'ai pas testé mais je pense que cela devrait fonctionner.

Andrei CACIO
la source
8

Jest a une méthode toThrow(error)pour tester qu'une fonction lève lorsqu'elle est appelée.

Donc, dans votre cas, vous devriez l'appeler ainsi:

expect(t).toThrowError(TypeError);

Les docs

Alexmac
la source
1
Cela ne fonctionnerait pas pour le cas: jest.spyOn(service, 'create').mockImplementation(() => { throw new Error(); });si la méthode simulée createne l'est pas async.
Sergey
7

La plaisanterie moderne vous permet de faire plus de contrôles sur la valeur rejetée. Par exemple:

const request = Promise.reject({statusCode: 404})
await expect(request).rejects.toMatchObject({ statusCode: 500 });

échouera avec une erreur

Error: expect(received).rejects.toMatchObject(expected)

- Expected
+ Received

  Object {
-   "statusCode": 500,
+   "statusCode": 404,
  }
Slava Baginov
la source
6

La documentation explique clairement comment procéder. Disons que j'ai une fonction qui prend deux paramètres et elle lancera une erreur si l'un d'entre eux l'est null.

function concatStr(str1, str2) {
  const isStr1 = str1 === null
  const isStr2 = str2 === null
  if(isStr1 || isStr2) {
    throw "Parameters can't be null"
  }
  ... // continue your code

Votre test

describe("errors", () => {
  it("should error if any is null", () => {
    // notice that the expect has a function that returns the function under test
    expect(() => concatStr(null, "test")).toThrow()
  })
})
Gilbert
la source
4

Si vous travaillez avec Promises:

await expect(Promise.reject(new HttpException('Error message', 402)))
  .rejects.toThrowError(HttpException);
Željko Šević
la source
C'est super utile, merci de gagner mon temps !!
Aditya Kresna Permana le
3

J'ai fini par écrire une méthode pratique pour notre bibliothèque test-utils

/**
 *  Utility method to test for a specific error class and message in Jest
 * @param {fn, expectedErrorClass, expectedErrorMessage }
 * @example   failTest({
      fn: () => {
        return new MyObject({
          param: 'stuff'
        })
      },
      expectedErrorClass: MyError,
      expectedErrorMessage: 'stuff not yet implemented'
    })
 */
  failTest: ({ fn, expectedErrorClass, expectedErrorMessage }) => {
    try {
      fn()
      expect(true).toBeFalsy()
    } catch (err) {
      let isExpectedErr = err instanceof expectedErrorClass
      expect(isExpectedErr).toBeTruthy()
      expect(err.message).toBe(expectedErrorMessage)
    }
  }
kpollock
la source
La même chose peut être faite en utilisant les fonctionnalités propres à Jests. Voir ma réponse pour savoir comment cela peut être fait - stackoverflow.com/a/58103698/3361387
Kenny Body
3

Suite à l'article de Peter Danis, je voulais juste souligner la partie de sa solution impliquant "[passer] une fonction dans expect (fonction) .toThrow (vide ou type d'erreur)".

Dans Jest, lorsque vous testez un cas où une erreur devrait être générée, dans votre wrapping expect () de la fonction en cours de test, vous devez fournir une couche de wrapping de fonction de flèche supplémentaire pour que cela fonctionne. C'est à dire

Mauvaise (mais l'approche logique de la plupart des gens):

expect(functionUnderTesting();).toThrow(ErrorTypeOrErrorMessage);

Droite:

expect(() => { functionUnderTesting(); }).toThrow(ErrorTypeOrErrorMessage);

C'est très étrange mais cela devrait permettre de réussir le test.

Adrian
la source
1

essayer
expect(t).rejects.toThrow()

Razim Saidov
la source
4
Pourquoi try? il n'y a pas d'essayer - mais de répondre. Si c'est la réponse, veuillez donner plus de détails. qu'est-ce que vous ajoutez à la réponse existante?
dWinder
7
Je pense que @Razim disait que vous devriez essayer la solution, pas utiliser une capture d'essai.
Tom