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.