Mocha / Chai expect.to.throw nie wyłapują zgłaszanych błędów

257

Mam problemy z expect.to.throwuruchomieniem Chai w teście dla mojej aplikacji node.js. Test wciąż nie powiedzie się w przypadku wyrzuconego błędu, ale jeśli opakuję przypadek testowy podczas próby złapania i potwierdzenia złapanego błędu, zadziała.

Nie expect.to.throwdziała tak, jak myślę, że powinno lub coś takiego?

it('should throw an error if you try to get an undefined property', function (done) {
  var params = { a: 'test', b: 'test', c: 'test' };
  var model = new TestModel(MOCK_REQUEST, params);

  // neither of these work
  expect(model.get('z')).to.throw('Property does not exist in model schema.');
  expect(model.get('z')).to.throw(new Error('Property does not exist in model schema.'));

  // this works
  try { 
    model.get('z'); 
  }
  catch(err) {
    expect(err).to.eql(new Error('Property does not exist in model schema.'));
  }

  done();
});

Awaria:

19 passing (25ms)
  1 failing

  1) Model Base should throw an error if you try to get an undefined property:
     Error: Property does not exist in model schema.
do re mi
źródło

Odpowiedzi:

339

Musisz przekazać funkcję do expect. Lubię to:

expect(model.get.bind(model, 'z')).to.throw('Property does not exist in model schema.');
expect(model.get.bind(model, 'z')).to.throw(new Error('Property does not exist in model schema.'));

Sposób, w jaki to robią, trzeba przejechać do expectw wyniku wywołania model.get('z'). Ale aby sprawdzić, czy coś zostanie wyrzucone, musisz przekazać funkcję expect, która się expectwywoła. bindMetoda stosowana powyżej tworzy nową funkcję, która po nazwie zadzwoni model.getz thiszestawem do wartości modeli jako pierwszy argument 'z'.

Dobre wyjaśnienie bindmożna znaleźć tutaj .

Louis
źródło
Przekazałem funkcję, prawda? modelinstancja ma funkcję get, którą przekazałem / wywołałem w oczekiwaniu.
doremi
Nie, zobacz wyjaśnienie, które dodałem podczas pisania komentarza.
Louis
47
Nie. Dlaczego dokumenty ( chaijs.com/api/bdd/#throw ) nie pokazują tego użycia wiązania? Wydaje się, że najczęstszym scenariuszem testowania to.throwjest testowanie określonego warunku w funkcji, który wymaga wywołania tej funkcji z niepoprawnym stanem / argumentami. (Jeśli o to chodzi ... dlaczego deeplinks chaijs.com nie jest tak naprawdę deeplink?)
ericsoco
Jeśli zdasz kilka parametrów, które nie powinny rzucić, test jest jednak pozytywny.
Alexandros Spyropoulos
6
Zauważ, że to nie zadziała (od września 2017 r.) Dla funkcji asynchronicznych: patrz github.com/chaijs/chai/issues/882#issuecomment-322131680 i powiązana dyskusja.
ChrisV
175

Jak mówi ta odpowiedź , możesz po prostu owinąć swój kod anonimową funkcją:

expect(function(){
    model.get('z');
}).to.throw('Property does not exist in model schema.');
twiz
źródło
7
Nie działa to w przypadku wywołań funkcji asynchronicznych. Załóżmy, że model.get jest asynchroniczny, który zwraca obietnicę. Jednak generuje błąd. Jeśli spróbuję zastosować powyższe podejście, będzie to „Upłynął limit czasu”, ponieważ musimy powiadomić „gotowe” mokce. Jednocześnie nie mogę wypróbować, expect(function(){ model.get('z'); }).to.throw('Property does not exist in model schema.').notify(done); ponieważ nie ma metody powiadamiania.
Anand N
@AnandN Jeśli rozumiem twój problem, brzmi to tak, jakbyś po prostu musiał zmienić kod w celu obsługi błędu. Czy nieobsługiwany błąd funkcji asynchronicznej nie będzie również problemem w Twojej aplikacji?
twiz
2
Dzięki twiz za twoją odpowiedź. Pracujemy w zintegrowanym środowisku, moduł korzystający dba o wyjątki. Problem polega na tym, gdy próbujemy uruchomić testy jednostkowe. W końcu zastosowaliśmy poniższe podejście, aby to catch (err) { expect(err).equal('Error message to be checked'); done(); }
zadziałało
1
Dobre rozwiązanie, z wyjątkiem sytuacji, gdy używasz thiswewnątrz wywoływanej funkcji. To .bindjest właściwa droga.
rabbitco
@AnandN Asynchroniczne wywołanie funkcji nie rzuca , odrzuca s. Na przyszłość, chai-as-obiecał radzi sobie z tym całkiem nieźle.
user5532169
85

A jeśli już używasz ES6 / ES2015, możesz również użyć funkcji strzałki. Jest to w zasadzie to samo, co używanie zwykłej anonimowej funkcji, ale jest krótsze.

expect(() => model.get('z')).to.throw('Property does not exist in model schema.');
Daniel T.
źródło
MOŻE BYĆ problem z tym, ponieważ funkcje strzałek obejmują otaczający ich zakresthis
Eric Hodonsky,
1
@ Relic Tak, bardzo prawdziwe. Może to być również dużą zaletą funkcji strzałek. Funkcje strzałek „dziedziczą” thispo zakresie, w którym zostały utworzone. Często może to być zaletą, ponieważ pozwala uniknąć konieczności ręcznego wprowadzania bindfunkcji do ich thisobiektu.
Stijn de Witt
@StijndeWitt nie jest to ani zaleta ani wada, lecz kontrola zakresu i celowa. W rzeczywistości jest to cukier składniowy do użycia bindi zawsze wiąże się thisz zakresem nadrzędnym. Moim zamiarem w komentarzu było jedynie upewnienie się, że czytelnicy są świadomi potencjalnego upadku.
Eric Hodonsky,
1
@ Relic Tak Zgadzam się z tobą. Może być wykorzystany z korzyścią i może być dobrym powodem do użycia funkcji strzałki.
Stijn de Witt
75

To pytanie ma wiele, wiele duplikatów, w tym pytania nie wspominające o bibliotece asercji Chai. Oto podstawy zebrane razem:

Asercja musi wywołać funkcję, zamiast natychmiastowej oceny.

assert.throws(x.y.z);      
   // FAIL.  x.y.z throws an exception, which immediately exits the
   // enclosing block, so assert.throw() not called.
assert.throws(()=>x.y.z);  
   // assert.throw() is called with a function, which only throws
   // when assert.throw executes the function.
assert.throws(function () { x.y.z });   
   // if you cannot use ES6 at work
function badReference() { x.y.z }; assert.throws(badReference);  
   // for the verbose
assert.throws(()=>model.get(z));  
   // the specific example given.
homegrownAssertThrows(model.get, z);
   //  a style common in Python, but not in JavaScript

Możesz sprawdzić określone błędy za pomocą dowolnej biblioteki asercji:

Węzeł

  assert.throws(() => x.y.z);
  assert.throws(() => x.y.z, ReferenceError);
  assert.throws(() => x.y.z, ReferenceError, /is not defined/);
  assert.throws(() => x.y.z, /is not defined/);
  assert.doesNotThrow(() => 42);
  assert.throws(() => x.y.z, Error);
  assert.throws(() => model.get.z, /Property does not exist in model schema./)

Powinien

  should.throws(() => x.y.z);
  should.throws(() => x.y.z, ReferenceError);
  should.throws(() => x.y.z, ReferenceError, /is not defined/);
  should.throws(() => x.y.z, /is not defined/);
  should.doesNotThrow(() => 42);
  should.throws(() => x.y.z, Error);
  should.throws(() => model.get.z, /Property does not exist in model schema./)

Chai Expect

  expect(() => x.y.z).to.throw();
  expect(() => x.y.z).to.throw(ReferenceError);
  expect(() => x.y.z).to.throw(ReferenceError, /is not defined/);
  expect(() => x.y.z).to.throw(/is not defined/);
  expect(() => 42).not.to.throw();
  expect(() => x.y.z).to.throw(Error);
  expect(() => model.get.z).to.throw(/Property does not exist in model schema./);

Musisz poradzić sobie z wyjątkami, które „uciekają” z testu

it('should handle escaped errors', function () {
  try {
    expect(() => x.y.z).not.to.throw(RangeError);
  } catch (err) {
    expect(err).to.be.a(ReferenceError);
  }
});

Na początku może to wydawać się mylące. Podobnie jak jazda na rowerze, po prostu „kliknie” na zawsze, gdy kliknie.

Charles Merriam
źródło
14

przykłady z dokumentu ...;)

ponieważ polegasz na thiskontekście:

  • który jest tracony, gdy funkcja jest wywoływana przez .throw
  • nie ma sposobu, aby wiedzieć, co to ma być

musisz użyć jednej z tych opcji:

  • zawiń metodę lub wywołanie funkcji do innej funkcji
  • powiązać kontekst

    // wrap the method or function call inside of another function
    expect(function () { cat.meow(); }).to.throw();  // Function expression
    expect(() => cat.meow()).to.throw();             // ES6 arrow function
    
    // bind the context
    expect(cat.meow.bind(cat)).to.throw();           // Bind
Michał Miky Jankovský
źródło
Tak też to robię. Uważam, że implementacja ES6 jest zdecydowanie najbardziej czytelna
relief.melone
1

Inna możliwa implementacja, bardziej kłopotliwa niż rozwiązanie .bind (), ale taka, która pomaga uczynić punkt oczekiwany () wymagającym funkcji, która zapewnia thiskontekst dla omawianej funkcji, można użyć call()np.

expect(function() {model.get.call(model, 'z');}).to.throw('...');

SeanOlson
źródło
0

Znalazłem dobry sposób na obejście tego:

// The test, BDD style
it ("unsupported site", () => {
    The.function(myFunc)
    .with.arguments({url:"https://www.ebay.com/"})
    .should.throw(/unsupported/);
});


// The function that does the magic: (lang:TypeScript)
export const The = {
    'function': (func:Function) => ({
        'with': ({
            'arguments': function (...args:any) {
                return () => func(...args);
            }
        })
    })
};

Jest o wiele bardziej czytelny niż moja stara wersja:

it ("unsupported site", () => {
    const args = {url:"https://www.ebay.com/"}; //Arrange
    function check_unsupported_site() { myFunc(args) } //Act
    check_unsupported_site.should.throw(/unsupported/) //Assert
});
Dani-Br
źródło