Jak napisać test, który oczekuje, że błąd zostanie zgłoszony w Jasmine?

490

Próbuję napisać test dla Jasmine Test Framework, który oczekuje błędu. W tej chwili używam integracji Jasmine Node.js z GitHub .

W moim module Node mam następujący kod:

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

Teraz próbuję napisać test, który oczekuje tego błędu:

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

Próbowałem również Error()i kilka innych wariantów i po prostu nie mogę dowiedzieć się, jak to zrobić.

echox
źródło
4
Aby przekazać argumenty do testowanej funkcji, bez korzystania z funkcji anonimowej, spróbuj Function.bind: stackoverflow.com/a/13233194/294855
Danyal Aytekin

Odpowiedzi:

802

powinieneś przekazać funkcję do expect(...)wywołania. Kod, który masz tutaj:

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

próbuje zadzwonić parser.parse(raw) w celu przekazania wyniku do expect(...),

Zamiast tego spróbuj użyć anonimowej funkcji:

expect( function(){ parser.parse(raw); } ).toThrow(new Error("Parsing is not possible"));
Pete Hodgson
źródło
28
Jeśli nie musisz też przekazywać argumentów, możesz również przekazać funkcję, której się spodziewasz:expect(parser.parse).toThrow(...)
SubmittedDenied
60
Pomocna wskazówka: możesz po prostu zadzwonić expect(blah).toThrow(). Brak argumentów oznacza sprawdzenie, czy w ogóle rzuca. Nie wymaga dopasowania ciągów. Zobacz także: stackoverflow.com/a/9525172/1804678
Jess
1
Moim zdaniem bardziej oczywiste jest zamierzenie testu podczas pakowania anonimowej funkcji. Ponadto pozostaje spójny we wszystkich testach, gdy na przykład musisz przekazać parametry do funkcji celu, aby ją rzucić.
Beez
10
@ SubmittedDenied: To nie działa w ogóle! Jeśli zostanie parser.parseużyty this, przekazanie go bez kontekstu przyniesie nieoczekiwane rezultaty. Możesz przejść parser.parse.bind(parser), ale szczerze mówiąc ... anonimowa funkcja byłaby bardziej elegancka.
mhelvens
2
@LanceK Przykro mi z powodu nekrozy, ale powodem, dla którego musisz przekazać funkcję, jest ocena wartości i zgłoszenie wyjątku, zanim jeszcze zostanie przekazany do oczekiwanej wartości.
1gLassitude
68

Ty używasz:

expect(fn).toThrow(e)

Ale jeśli spojrzysz na komentarz funkcji (oczekiwany jest ciąg znaków):

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

Przypuszczam, że powinieneś napisać to w ten sposób (używając lambda - anonimowej funkcji):

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

Potwierdza to poniższy przykład:

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

Douglas Crockford zdecydowanie zaleca takie podejście, zamiast używać „throw new Error ()” (sposób prototypowania):

throw {
   name: "Error",
   message: "Parsing is not possible"
}
Andrzej Śliwa
źródło
3
Właściwie patrząc na kod toThrow z przyjemnością przyjmie obiekt wyjątku / lub ciąg znaków. Sprawdź, na przykład, jakie połączenia wykonuje zgodnie z oczekiwaniami.
Pete Hodgson,
1
Wydaje się, że zezwala na łańcuch jako efekt uboczny łańcucha bez właściwości wiadomości
mpapis,
1
Wielkie dzięki, że się udało. Nadal przyjął odpowiedź Pete beacuse jego odpowiedź staje się on bardziej wyraźnie do mnie, że ja mam użyć lambda. Nadal +1 :-) Dzięki!
echox
16
Jeśli rzucisz obiekt zamiast błędu (jak w przykładzie na dole), nie uzyskasz śladu stosu w przeglądarkach, które go obsługują.
kybernetikos
2
@kybernetikos zaskakująco, nie do końca prawda; nadal otrzymasz ślad stosu wydrukowany w konsoli Chrome, jeśli rzucisz plik inny niż Error( jsfiddle.net/k1mxey8j ). Twój rzucony obiekt oczywiście nie będzie miał .stackwłaściwości, co może być ważne, jeśli chcesz skonfigurować automatyczne raportowanie błędów.
Mark Amery
24

Bardziej eleganckim rozwiązaniem niż tworzenie anonimowej funkcji, której jedynym celem jest owijanie innej, jest użycie bindfunkcji es5 . Funkcja wiązania tworzy nową funkcję, która po wywołaniu ma swojąthis ustawione słowo kluczowe na podaną wartość, z podaną sekwencją argumentów poprzedzającą podaną podczas wywołania nowej funkcji.

Zamiast:

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

Rozważać:

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

Składnia powiązania umożliwia testowanie funkcji o różnych thiswartościach i, moim zdaniem, sprawia, że ​​test jest bardziej czytelny. Zobacz także: https://stackoverflow.com/a/13233194/1248889

Jonathan Gawrych
źródło
23

Zastępuję dopasowywanie Jasmine toThrow następującym, co pozwala dopasować właściwość name wyjątku lub właściwość message. Dla mnie sprawia to, że testy są łatwiejsze do pisania i mniej kruche, ponieważ mogę wykonać następujące czynności:

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

a następnie przetestuj za pomocą:

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

To pozwala mi później ulepszyć komunikat o wyjątku bez przerywania testów, gdy ważne jest, że wygenerował oczekiwany typ wyjątku.

Jest to zamiennik programu toThrow, który pozwala na:

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
źródło
4
Ładne podejście, ale czy {nazwa: „...”, komunikat: „...”} jest właściwym obiektem błędu w JavaScript?
Marc
1
Niezły komentarz @Marc. Masz rację, właściwość name nie jest standardowa. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… , ale czy to tak źle?
Jess
4
@Jake! Znalazłem lepszy sposób !!!! Możesz po prostu zadzwonić expect(blah).toThrow(). Brak argumentów oznacza sprawdzenie, czy w ogóle rzuca. Nie wymaga dopasowania ciągów. Zobacz także: stackoverflow.com/a/9525172/1804678
Jess
5
Dzięki Jess - to prawda, ale może to generować inny błąd, na przykład TypeError, a mój test nie przejdzie poprawnie, maskując prawdziwy błąd.
Jake
4
Możesz teraz także użyć RegEx jako argumentu funkcji toThrow ().
Tony O'Hagan,
21

Jak wspomniano wcześniej, należy przekazać toThrowfunkcję, ponieważ jest to funkcja opisywana w teście: „Spodziewam się, że ta funkcja wyrzuci x”

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

Jeśli używasz Jasmine-Matchers , możesz również użyć jednego z poniższych, jeśli odpowiadają sytuacji;

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

lub

// 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
źródło
3
Jest expect(foo).toThrowError(TypeError);w Jasmine 2.5: jasmine.github.io/2.5/introduction
Benny Neugebauer
9

Wiem, że to więcej kodu, ale możesz także:

try
   do something
   @fail Error("should send a Exception")
 catch e
   expect(e.name).toBe "BLA_ERROR"
   expect(e.message).toBe 'Message'
Tolbard
źródło
Zwykle podoba mi się aspekt „samodokumentowania
6

Dla miłośników kawy

expect( => someMethodCall(arg1, arg2)).toThrow()
Fernandohur
źródło
3

Dla każdego, kto nadal może napotykać ten problem, dla mnie opublikowane rozwiązanie nie działało i nadal zgłaszało ten błąd: Error: Expected function to throw an exception. później zdałem sobie sprawę, że funkcja, którą spodziewałem się zgłosić, była funkcją asynchroniczną i spodziewała się obiecać zostać odrzuconym, a następnie rzucić błąd, i to właśnie robiłem w moim kodzie:

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

i to właśnie zrobiłem w teście i zadziałało:

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
źródło
Dzięki za to. Byłem bardzo zdezorientowany, ale twój komentarz ma doskonały sens. expectAsync Rozwiązałem
Benjamin