Mokka / Chai erwarten, dass sie keine geworfenen Fehler abfangen

257

Ich habe Probleme damit, dass Chai's expect.to.throwin einem Test für meine node.js-App funktioniert. Der Test schlägt bei dem ausgelösten Fehler immer wieder fehl, aber wenn ich den Testfall mit dem Versuch abfange, den abgefangenen Fehler abzufangen und zu bestätigen, funktioniert er.

Funktioniert expect.to.thrownicht so wie ich es mir vorstelle oder so?

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

Der Fehlschlag:

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.
doremi
quelle

Antworten:

339

Sie müssen eine Funktion an übergeben expect. So was:

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

Die Art und Weise Sie es tun, sind vorbei Sie expectdas Ergebnis des Aufrufs model.get('z'). Aber um zu testen, ob etwas geworfen wird, müssen Sie eine Funktion übergeben expect, die expectsich selbst aufruft. Die bindoben verwendete Methode erstellt eine neue Funktion, die beim Aufruf model.getmit thisset auf den Wert von modelund dem ersten Argument auf gesetzt wird 'z'.

Eine gute Erklärung binddazu finden Sie hier .

Louis
quelle
Ich habe eine Funktion bestanden, nicht wahr? modelInstanz hat eine Funktion namens get, die ich übergeben / aufgerufen habe.
Doremi
Nein, siehe die Erklärung, die ich hinzugefügt habe, als Sie Ihren Kommentar geschrieben haben.
Louis
47
Uff. Warum demonstrieren die Dokumente ( chaijs.com/api/bdd/#throw ) diese Verwendung von bind nicht? Das häufigste Testszenario scheint das to.throwTesten einer bestimmten Bedingung innerhalb einer Funktion zu sein, für die diese Funktion mit dem ungültigen Status / den ungültigen Argumenten aufgerufen werden muss. (Übrigens ... warum deeplinks nicht von chaijs.com tatsächlich deeplink?)
ericsoco
Wenn Sie einige Parameter übergeben, die nicht ausgelöst werden sollten, ist der Test dennoch ein Bestehen.
Alexandros Spyropoulos
6
Beachten Sie, dass dies (ab September 2017) für asynchrone Funktionen nicht funktioniert: siehe github.com/chaijs/chai/issues/882#issuecomment-322131680 und die zugehörige Diskussion.
ChrisV
175

Wie diese Antwort sagt , können Sie Ihren Code auch einfach in eine anonyme Funktion wie diese einschließen:

expect(function(){
    model.get('z');
}).to.throw('Property does not exist in model schema.');
twiz
quelle
7
Dies funktioniert nicht bei asynchronen Funktionsaufrufen. Angenommen, model.get ist asynchron und gibt Versprechen zurück. Es wird jedoch ein Fehler ausgegeben. Wenn ich den obigen Ansatz versuche, ist es "Zeitüberschreitung", da wir Mokka "erledigt" mitteilen müssen. Gleichzeitig kann ich es nicht versuchen, expect(function(){ model.get('z'); }).to.throw('Property does not exist in model schema.').notify(done); da es keine Benachrichtigungsmethode gibt.
Anand N
@AnandN Wenn ich Ihr Problem verstehe, müssen Sie lediglich Ihren Code umgestalten, um den Fehler zu beheben. Wird der nicht behandelte Fehler in der Async-Funktion nicht auch in Ihrer eigentlichen App ein Problem sein?
Twiz
2
Danke twiz für deine Antwort. Wir arbeiten in einer integrierten Umgebung, das using-Modul sorgt dafür, dass die Ausnahmen abgefangen werden. Das Problem ist also, wenn wir versuchen, Unit-Testfälle auszuführen. Schließlich haben wir den folgenden Ansatz verwendet, um es zum catch (err) { expect(err).equal('Error message to be checked'); done(); }
Anand N
1
Gute Lösung, außer wenn Sie thisinnerhalb der aufzurufenden Funktion arbeiten. Dann .bindist der richtige Weg.
Rabbitco
@AnandN Asynchrone Funktionsaufruf nicht werfen , es ablehnen s. Zum späteren Nachschlagen handhabt Chai-as-Promised dies recht gut.
user5532169
85

Und wenn Sie bereits ES6 / ES2015 verwenden, können Sie auch eine Pfeilfunktion verwenden. Dies entspricht im Wesentlichen der Verwendung einer normalen anonymen Funktion, ist jedoch kürzer.

expect(() => model.get('z')).to.throw('Property does not exist in model schema.');
Daniel T.
quelle
Es kann ein Problem damit geben, weil this
Pfeilfunktionen
1
@ Relic Ja, sehr wahr. Dies kann auch ein großer Vorteil von Pfeilfunktionen sein. Pfeilfunktionen "erben" thisvon dem Bereich, in dem sie erstellt wurden. Dies kann häufig von Vorteil sein, da keine manuellen bindFunktionen für ihr thisObjekt erforderlich sind .
Stijn de Witt
@StijndeWitt dies ist kein Vorteil oder Nachteil, es ist Bereichskontrolle und beabsichtigt. Es ist eigentlich Syntaxzucker für die Verwendung bindund immer Bindung an thisden übergeordneten Bereich. Meine Absicht in dem Kommentar war nur sicherzustellen, dass die Leser über einen möglichen Fall von Gruben informiert waren.
Eric Hodonsky
1
@ Relic Ja, ich stimme dir zu. Es kann zu einem Vorteil verwendet werden und kann ein guter Grund sein, eine Pfeilfunktion zu verwenden.
Stijn de Witt
75

Diese Frage enthält viele, viele Duplikate, einschließlich Fragen, in denen die Chai-Assertionsbibliothek nicht erwähnt wird. Hier sind die Grundlagen zusammengefasst:

Die Assertion muss die Funktion aufrufen, anstatt sie sofort auszuwerten.

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

Sie können mit einer beliebigen Assertionsbibliothek nach bestimmten Fehlern suchen:

Knoten

  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./)

Sollte

  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 erwarten

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

Sie müssen Ausnahmen behandeln, die dem Test entkommen

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

Dies kann zunächst verwirrend aussehen. Wie beim Fahrradfahren "klickt" es für immer, sobald es klickt.

Charles Merriam
quelle
14

Beispiele aus doc ...;)

weil Sie sich auf den thisKontext verlassen:

  • Dies geht verloren, wenn die Funktion von .throw aufgerufen wird
  • Es gibt keine Möglichkeit zu wissen, was das sein soll

Sie müssen eine der folgenden Optionen verwenden:

  • Wickeln Sie den Methoden- oder Funktionsaufruf in eine andere Funktion ein
  • binde den Kontext

    // 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
Michal Miky Jankovský
quelle
So mache ich es auch. Ich finde, dass die ES6-Implementierung bei weitem die am besten lesbare ist
relief.melone
1

Eine andere mögliche Implementierung, die umständlicher ist als die .bind () -Lösung, aber eine, die dabei hilft, den erwarteten Punkt () zu verdeutlichen, erfordert eine Funktion, die einen thisKontext für die abgedeckte Funktion bereitstellt. Sie können call()z.

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

SeanOlson
quelle
0

Ich habe einen schönen Weg gefunden, um es zu umgehen:

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

Es ist viel besser lesbar als meine alte Version:

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
quelle