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?

bartsmykla
źródło

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);
});
PeterDanis
źródło
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

bodolsog
źródło
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')
Tal Joffe
źródło
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ę.

Kenny Body
źródło
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ć.

Andrei CACIO
źródło
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,
  }
Slava Baginov
źródło
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);
Željko Šević
źródło
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 .

alexmac
źródło
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()
  })
})
Gilbert
źródło
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.

Adrian
źródło
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)
    }
  }
kpollock
źródło
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()
Razim Saidov
źródło
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