Jak przetestować typ zgłoszonego wyjątku w Jest


188

Pracuję z kodem, w którym muszę przetestować typ wyjątku zgłaszanego przez funkcję (czy jest to TypeError, ReferenceError itp.?).

Mój obecny framework testowy to AVA i mogę go przetestować jako t.throwsmetodę drugiego argumentu , jak tutaj:

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

Zacząłem przepisywać moje testy w Jest i nie mogłem znaleźć, jak to łatwo zrobić. Czy to w ogóle możliwe?

Odpowiedzi:


256

W Jest trzeba przekazać funkcję do expa (funkcji) .toThrow (pusta lub typ błędu).

Przykład:

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

Jeśli chcesz przetestować istniejącą funkcję, czy rzuca z zestawem argumentów, musisz ją opakować w anonimową funkcję wects ().

Przykład:

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

Dobrze - czy mogę zapytać, dlaczego potrzebne jest opakowanie funkcji anonimowej? Z opakowaniem działa, ale nie bez niego.
rags2riches

@ rags2riches funkcja anonimowa jest wymagana, ponieważ expect(x).toThrow()wymaga xodwołania do funkcji, która generuje. Jeśli zamiast tego zdasz expect(x()).toThrow(), JavaScript rozwiąże problem x(), co prawdopodobnie spowoduje natychmiastowy błąd i prawdopodobnie zakończy się niepowodzeniem.
Scott Chow,

88

To trochę dziwne, ale działa, a IMHO jest czytelne:

it('should throw Error with message \'UNKNOWN ERROR\' when no parameters 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");
  }
});

CatchBlok łapie swój wyjątek, a następnie można przetestować w podniesione Error. Dziwne expect(true).toBe(false);jest potrzebne, aby nie zdać testu, jeśli oczekiwane Errornie zostanie rzucone. W przeciwnym razie ta linia nigdy nie jest osiągalna ( Errorpowinna zostać podniesiona przed nimi).

@Kenny Body zasugerował lepsze rozwiązanie, które poprawi jakość kodu, jeśli używasz expect.assertions():

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

Zobacz oryginalną odpowiedź z dodatkowymi wyjaśnieniami: Jak przetestować typ rzuconego wyjątku w Jest


20
Jest to bardzo rozwlekły sposób testowania wyjątków, gdy Jest już ma oczekiwanie. ToThrow () sposób sprawdzania wyjątków: jestjs.io/docs/en/expect.html#tothrowerror
gomisha

5
Tak, ale testuje tylko typ, a nie wiadomość lub inną treść, a pytanie dotyczyło wiadomości testowej, a nie typu.
bodolsog

2
Hah. Naprawdę podoba mi się ten, ponieważ mój kod musi przetestować wartość zgłoszonego błędu, więc potrzebuję instancji. Napisałbym błędne oczekiwanie, jak expect('here').not.toBe('here');dla samej zabawy :-)
Valery

4
@Valery lub: expect('to be').not.toBe('to be')w stylu Szekspira.
Michiel van der Blonk

2
najbardziej niedoceniana odpowiedź!
Edwin O.

53

Używam nieco bardziej zwięzłej wersji:

expect(() => {
  // Code block that should throw error
}).toThrow(TypeError) // Or .toThrow('expectedErrorMessage')

5
Krótkie i precyzyjne.
flapjack

36

Z mojego (choć ograniczonego) kontaktu z Jest, stwierdziłem, że expect().toThrow()jest odpowiedni, jeśli chcesz tylko przetestować, że wyrzucany jest błąd określonego typu:

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

Lub wyświetlany jest błąd z określoną wiadomością:

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

Jeśli spróbujesz zrobić jedno i drugie, otrzymasz fałszywie pozytywny wynik. Na przykład, jeśli twój kod wyrzuca RangeError('Something bad happened!'), ten test przejdzie:

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

Odpowiedź bodolsoga, która sugeruje użycie try / catch, jest bliska, ale zamiast oczekiwać, że prawda będzie fałszywa, aby zapewnić trafienie oczekiwanych asercji w catch, możesz zamiast tego użyć expect.assertions(2)na początku testu, gdzie 2jest liczba oczekiwanych stwierdzeń . Czuję, że to dokładniej oddaje intencję testu.

Pełny przykład testowania rodzaju i komunikatu błędu:

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

Jeśli functionUnderTest()nie nie wygeneruje błąd, twierdzenia będzie trafienie, ale expect.assertions(2)nie powiedzie się, a test nie powiedzie się.


D'oh. Zawsze zapominam o oczekiwaniu funkcji wielokrotnych asercji w Jest (być może osobiście nie uważam jej za najbardziej intuicyjną, ale zdecydowanie działa w takich przypadkach!).
kpollock

To zadziałało dla mnie doskonale. Należy to wykorzystać.
Ankit Tanna

expect.hasAssertions()może być lepszą alternatywą, gdy test nie ma żadnych potwierdzeń na zewnątrz catch, ponieważ nie musisz aktualizować liczby, jeśli dodasz / usuniesz potwierdzenia.
André Sassi

Innym sposobem sprawdzenia typu i wiadomości jest użycie toThrowWithMessage(type, message)z projektu rozszerzonego.
Mark Doliner

13

Sam tego nie próbowałem, ale sugerowałbym użycie asercji Jest toThrow . Więc myślę, że twój przykład wyglądałby mniej więcej tak:

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

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

Ponownie, nie testowałem tego, ale myślę, że powinno działać.


11

Modern Jest pozwala na więcej sprawdzeń wartości odrzuconej. Na przykład:

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

zakończy się błędem

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

- Expected
+ Received

  Object {
-   "statusCode": 500,
+   "statusCode": 404,
  }

W odniesieniu do „większej liczby sprawdzeń odrzuconej wartości” : Dlaczego jest to przydatne? Czy możesz rozwinąć? Najlepiej edytując swoją odpowiedź ( bez opcji „Edytuj”, „Aktualizuj” itp.).
Peter Mortensen

9

W przypadku, gdy pracujesz z Promises:

await expect(Promise.reject(new HttpException('Error message', 402)))
  .rejects.toThrowError(HttpException);

Jest to bardzo pomocne, dzięki za zaoszczędzenie czasu!
Aditya Kresna Permana

9

Jest ma metodę, toThrow(error)aby sprawdzić, czy funkcja wyrzuca, gdy jest wywoływana.

Więc w twoim przypadku powinieneś to tak nazwać:

expect(t).toThrowError(TypeError);

Dokumentacja .


1
To nie zadziała w tym przypadku: jest.spyOn(service, 'create').mockImplementation(() => { throw new Error(); });jeśli wyszydzana metoda createnie jest async.
Sergey

6

Dokumentacja jasno opisuje, jak to zrobić. Powiedzmy, że mam funkcję, która przyjmuje dwa parametry i zgłosi błąd, jeśli jeden z nich jest 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

Twój 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()
  })
})

4

W nawiązaniu do postu Petera Danisa , chciałem tylko podkreślić część jego rozwiązania polegającą na „[przekazaniu] funkcji do funkcji oczekującej (funkcja) .to rzucać (pusta lub rodzaj błędu)”.

W Jest, gdy testujesz przypadek, w którym powinien zostać wyrzucony błąd, w ramach pakowania funkcji w trakcie exp () testowanej funkcji musisz zapewnić jedną dodatkową warstwę zawijania funkcji strzałkowej, aby zadziałała. To znaczy

Błędne (ale logiczne podejście większości ludzi):

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

Dobrze:

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

To bardzo dziwne, ale powinno sprawić, że testy przebiegną pomyślnie.


1
Dziękuję za odpowiedź. I to niewiarygodne, jak dokumentacja Jest może utrudniać sprawę przez ukryte ważne informacje o funkcjonalności.
Pablo Lopes

3

Skończyło się na napisaniu wygodnej metody dla naszej biblioteki narzędzi testowych

/**
 *  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)
    }
  }

To samo można zrobić za pomocą własnych funkcji Jests. Zobacz moją odpowiedź, jak to zrobić - stackoverflow.com/a/58103698/3361387
Kenny Body

Również osobny projekt rozszerzony jest z toThrowWithMessage(type, message)dopasowanym, który jest całkiem niezły.
Mark Doliner

1

Próbować:

expect(t).rejects.toThrow()

4
Czemu try ? nie ma próby - ale odpowiedź. Jeśli to jest odpowiedź, opisz więcej. co dodajesz do istniejącej odpowiedzi?
dWinder

8
Myślę, że @Razim mówił, że powinieneś wypróbować rozwiązanie, a nie używać try catch.
Tom
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.