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ć.


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"));

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"
}

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


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

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

3
Jest expect(foo).toThrowError(TypeError);w Jasmine 2.5: jasmine.github.io/2.5/introduction
— Benny Neugebauer



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

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