Gibt es eine Möglichkeit, Chai dazu zu bringen, mit asynchronen Mokka-Tests zu arbeiten?

81

Ich führe einige asynchrone Tests in Mocha mit dem Browser Runner aus und versuche, Chais Assertion Style Assertions zu verwenden:

window.expect = chai.expect;
describe('my test', function() {
  it('should do something', function (done) {
    setTimeout(function () {
      expect(true).to.equal(false);
    }, 100);
  }
}

Dies gibt mir nicht die normale fehlgeschlagene Bestätigungsnachricht, sondern ich erhalte:

Error: the string "Uncaught AssertionError: expected true to equal false" was thrown, throw an Error :)
    at Runner.fail (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3475:11)
    at Runner.uncaught (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3748:8)
    at uncaught (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3778:10)

Es fängt also offensichtlich den Fehler auf, es zeigt ihn einfach nicht richtig an. Irgendwelche Ideen, wie das geht? Ich denke, ich könnte mit einem Fehlerobjekt einfach "erledigt" nennen, aber dann verliere ich die Eleganz von etwas wie Chai und es wird sehr klobig ...

Thomas Parslow
quelle
Das Problem ist mit dem Browserseiten-Mokka. Weitere Informationen hierzu finden Sie unter github.com/visionmedia/mocha/pull/278 .
Elliot Foster
Ab 2020 sollten Sie sich das chai-as-promisedPlugin ansehen ...
Elmar Zander

Antworten:

96

Ihr asynchroner Test generiert bei fehlgeschlagenen expect()Aktionen eine Ausnahme, die nicht erfasst werden kann, it()da die Ausnahme außerhalb des it()Gültigkeitsbereichs des Bereichs ausgelöst wird .

Die erfasste Ausnahme, die angezeigt wird, wird mit process.on('uncaughtException')unter Knoten oder window.onerror()im Browser erfasst .

Um dieses Problem zu beheben, müssen Sie die Ausnahme innerhalb der von aufgerufenen asynchronen Funktion setTimeout()erfassen, um done()mit der Ausnahme als erstem Parameter aufzurufen . Sie müssen auch done()ohne Parameter aufrufen , um den Erfolg anzuzeigen. Andernfalls meldet Mokka einen Timeout-Fehler, da Ihre Testfunktion niemals signalisiert hätte, dass dies durchgeführt wurde:

window.expect = chai.expect;

describe( 'my test', function() {
  it( 'should do something', function ( done ) {
    // done() is provided by it() to indicate asynchronous completion
    // call done() with no parameter to indicate that it() is done() and successful
    // or with an error to indicate that it() failed
    setTimeout( function () {
      // Called from the event loop, not it()
      // So only the event loop could capture uncaught exceptions from here
      try {
        expect( true ).to.equal( false );
        done(); // success: call done with no parameter to indicate that it() is done()
      } catch( e ) {
        done( e ); // failure: call done with an error Object to indicate that it() failed
      }
    }, 100 );
    // returns immediately after setting timeout
    // so it() can no longer catch exception happening asynchronously
  }
}

Dies in all Ihren Testfällen zu tun, ist ärgerlich und nicht TROCKEN. Daher möchten Sie möglicherweise eine Funktion bereitstellen, die dies für Sie erledigt. Nennen wir diese Funktion check():

function check( done, f ) {
  try {
    f();
    done();
  } catch( e ) {
    done( e );
  }
}

Mit können check()Sie jetzt Ihre asynchronen Tests wie folgt umschreiben:

window.expect = chai.expect;

describe( 'my test', function() {
  it( 'should do something', function( done ) {
    setTimeout( function () {
      check( done, function() {
        expect( true ).to.equal( false );
      } );
    }, 100 );
  }
}
Jean Vincent
quelle
Ich habe gerade meinen vorherigen Kommentar gelöscht, nachdem mir klar wurde, dass das Bit, über das ich mich beschwert habe (setTimeout), tatsächlich von meiner Frage stammt. Es tut uns leid!!
Thomas Parslow
2
Die obige Antwort scheint falsch zu sein. Eine fehlgeschlagene Erwartung wird sofort ausgelöst und der Test mit einem bedeutungsvollen Fehler abgebrochen. Es ist kein komplizierter Versuch / Fang erforderlich. Ich habe es gerade mit einem Browsertest getestet.
Offirmo
3
Ich war mit diesem Problem zu kämpfen und fanden diese Blog - Post äußerst hilfreich: staxmanade.com/2015/11/...
RichardForrester
1
@ RichardForrester, sehr hilfreicher Beitrag. Vielen Dank! Damit diese Überprüfung mit Promises funktioniert, wird der Code unglaublich vereinfacht. Aber es muss mit Versprechungen sein (keine asynchrone Funktion).
Pedro R.
1
Ich möchte nur der Nachwelt mitteilen, dass genau dieses Problem mit dem Vue nexttick () (einem Wrapper für Versprechen) auftritt und auf die gleiche Weise behandelt werden kann.
Eli Albert
20

Hier sind meine bestandenen Tests für ES6 / ES2015-Versprechen und ES7 / ES2016 async / await. Hoffe, dies bietet eine schöne aktualisierte Antwort für alle, die dieses Thema erforschen:

import { expect } from 'chai'

describe('Mocha', () => {
  it('works synchronously', () => {
    expect(true).to.equal(true)
  })

  it('works ansyncronously', done => {
    setTimeout(() => {
      expect(true).to.equal(true)
      done()
    }, 4)
  })

  it('throws errors synchronously', () => {
    return true
    throw new Error('it works')
  })

  it('throws errors ansyncronously', done => {
    setTimeout(() => {
      return done()
      done(new Error('it works'))
    }, 4)
  })

  it('uses promises', () => {
    var testPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Hello')
      }, 4)
    })

    testPromise.then(result => {
      expect(result).to.equal('Hello')
    }, reason => {
      throw new Error(reason)
    })
  })

  it('uses es7 async/await', async (done) => {
    const testPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Hello')
      }, 4)
    })

    try {
      const result = await testPromise
      expect(result).to.equal('Hello')
      done()
    } catch(err) {
      done(err)
    }
  })

  /*
  *  Higher-order function for use with async/await (last test)
  */
  const mochaAsync = fn => {
    return async (done) => {
      try {
        await fn()
        done()
      } catch (err) {
        done(err)
      }
    }
  }

  it('uses a higher order function wrap around async', mochaAsync(async () => {
    const testPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Hello')
      }, 4)
    })

    expect(await testPromise).to.equal('Hello')
  }))
})
RichardForrester
quelle
@Pedro R. Ich habe geändert, um erledigt aus dem Versprechen Test zu entfernen. Wie Sie bereits betont haben, wird es nicht benötigt.
RichardForrester
13

Wenn Sie versprochen mögen, versuchen Sie Chai als versprochen + Q , die so etwas erlauben:

doSomethingAsync().should.eventually.equal("foo").notify(done);
xinthink
quelle
2

Ich habe dasselbe in der Mokka-Mailingliste gefragt. Sie sagten mir im Grunde Folgendes: um einen asynchronen Test mit Mocha und Chai zu schreiben:

  • Beginnen Sie den Test immer mit if (err) done(err);
  • Beenden Sie den Test immer mit done().

Es löste mein Problem und änderte keine einzige Zeile meines Codes dazwischen (unter anderem Chai-Erwartungen). Das setTimoutist nicht die Art und Weise Asynchron - Tests zu tun.

Hier ist der Link zur Diskussion in der Mailingliste .

DjebbZ
quelle
1
Die Diskussion, mit der Sie verlinkt haben, handelt von serverseitigem Chai und Mokka. Das Poster fragt nach Browserseiten- Mokka und Chai.
Elliot Foster
Das ist nicht das gleiche Problem. Die setTimeoutin dieser Frage als Beispiel verwendete Funktion weist keinen Fehler im Rückruf auf.
Sylvain
1

Ich habe ein Paket veröffentlicht, das dieses Problem behebt.

Installieren Sie zuerst das check-chaiPaket:

npm install --save check-chai

Verwenden Sie chai.use(checkChai);dann in Ihren Tests die chai.checkHilfsfunktion wie folgt:

var chai = require('chai');
var dirtyChai = require('dirty-chai');
var checkChai = require('check-chai');
var expect = chai.expect;
chai.use(dirtyChai);
chai.use(checkChai);

describe('test', function() {

  it('should do something', function(done) {

    // imagine you have some API call here
    // and it returns (err, res, body)
    var err = null;
    var res = {};
    var body = {};

    chai.check(done, function() {
      expect(err).to.be.a('null');
      expect(res).to.be.an('object');
      expect(body).to.be.an('object');
    });

  });

});

Per Gibt es eine Möglichkeit, Chai mit asynchronen Mokka-Tests zum Laufen zu bringen? Ich habe dies als NPM-Paket veröffentlicht.

Weitere Informationen finden Sie unter https://github.com/niftylettuce/check-chai .

Niftylettuce
quelle
1

Versuchen Sie es mit chaiAsPromised! Abgesehen davon, dass Sie einen hervorragenden Namen haben, können Sie folgende Anweisungen verwenden:

expect(asyncToResultingValue()).to.eventually.equal(true)

Kann bestätigen , funktioniert sehr gut für Mocha + Chai.

https://github.com/domenic/chai-as-promised

Manil
quelle
1

Sehr verwandt mit und inspiriert von Jean Vincents Antwort , verwenden wir eine Hilfsfunktion, die seiner Funktion ähnlich checkist, aber wir nennen sie eventuallystattdessen (dies hilft, sie mit den Namenskonventionen von Chai-as-versprochen in Einklang zu bringen). Es gibt eine Funktion zurück, die eine beliebige Anzahl von Argumenten akzeptiert und diese an den ursprünglichen Rückruf übergibt. Auf diese Weise können Sie einen zusätzlichen verschachtelten Funktionsblock in Ihren Tests entfernen und jede Art von asynchronem Rückruf verarbeiten. Hier steht es in ES2015:

function eventually(done, fn) {
  return (...args) => {
    try {
      fn(...args);
      done();
    } catch (err) {
      done(err);
    }
  };
};

Anwendungsbeispiel:

describe("my async test", function() {
  it("should fail", function(done) {
    setTimeout(eventually(done, (param1, param2) => {
      assert.equal(param1, "foo");   // this should pass
      assert.equal(param2, "bogus"); // this should fail
    }), 100, "foo", "bar");
  });
});
Ryan McGeary
quelle
1

Ich weiß, dass es viele Wiederholungsantworten und vorgeschlagene Pakete gibt, um dies zu lösen, aber ich habe nicht gesehen, dass die oben genannten einfachen Lösungen ein präzises Muster für die beiden Anwendungsfälle bieten. Ich poste dies als konsolidierte Antwort für andere, die Pasta kopieren möchten:

Ereignisrückrufe

function expectEventCallback(done, fn) {
  return function() {
    try { fn(...arguments); }
    catch(error) { return done(error); }
    done();
  };
}

Rückrufe im Knotenstil

function expectNodeCallback(done, fn) {
  return function(err, ...args) {
    if (err) { return done(err); }
    try { fn(...args); }
    catch(error) { return done(error); }
    done();
  };
}

Beispiel Verwendung

it('handles event callbacks', function(done) {
  something.on('event', expectEventCallback(done, (payload) => {
    expect(payload).to.have.propertry('foo');
  }));
});

it('handles node callbacks', function(done) {
  doSomething(expectNodeCallback(done, (payload) => {
    expect(payload).to.have.propertry('foo');
  }));
});
Sukima
quelle
0

Basierend auf diesem Link von @richardforrester http://staxmanade.com/2015/11/testing-asyncronous-code-with-mochajs-and-es7-async-await/ kann description ein zurückgegebenes Versprechen verwenden, wenn Sie das Erledigte weglassen Parameter.

Der einzige Nachteil ist, dass es dort ein Versprechen geben muss, keine asynchrone Funktion (Sie können es mit einem Versprechen abschließen, du). In diesem Fall kann der Code jedoch extrem reduziert werden.

Dabei werden Fehler entweder in der anfänglichen Funktion funcThatReturnsAPromise oder in den Erwartungen berücksichtigt:

it('should test Promises', function () { // <= done removed
    return testee.funcThatReturnsAPromise({'name': 'value'}) // <= return added
        .then(response => expect(response).to.have.property('ok', 1));
});
Pedro R.
quelle
0

Ich habe es gelöst, try/catchum eine Funktion zu extrahieren .

function asyncExpect(test, done){
    try{
        test();
        done();
    } catch(error){
        done(error);
    }
}

Dann it()rufe ich an:

it('shall update a host', function (done) {
            testee.insertHost({_id: 'host_id'})
                .then(response => {
                    asyncExpect(() => {
                        expect(response).to.have.property('ok', 1);
                        expect(response).to.have.property('nModified', 1);
                    }, done);
                });

        });

Es ist auch debuggbar.

Amio.io
quelle
0

Timer während Tests und Async klingen ziemlich rau. Es gibt eine Möglichkeit, dies mit einem vielversprechenden Ansatz zu erreichen.

const sendFormResp = async (obj) => {
    const result = await web.chat.postMessage({
        text: 'Hello world!',
    });
   return result
}

Diese asynchrone Funktion verwendet einen Webclient (in diesem Fall Slacks SDK). Das SDK kümmert sich um die Asynchronität des API-Aufrufs und gibt eine Nutzlast zurück. Wir können dann die Nutzlast in Chai testen, indem wir expectgegen das im asynchronen Versprechen zurückgegebene Objekt laufen .

describe("Slack Logic For Working Demo Environment", function (done) {
    it("Should return an object", () => {
        return sdkLogic.sendFormResp(testModels.workingModel).then(res => {
            expect(res).to.be.a("Object");
        })
    })
});
Justin Rice
quelle
-2

Was für mich bei icm Mocha / Chai sehr gut funktioniert hat, war der fakeTimer aus Sinons Bibliothek. Stellen Sie den Timer im Test bei Bedarf einfach vor.

var sinon = require('sinon');
clock = sinon.useFakeTimers();
// Do whatever. 
clock.tick( 30000 ); // Advances the JS clock 30 seconds.

Hat den zusätzlichen Vorteil, dass der Test schneller abgeschlossen wird.

TinkerTank
quelle
1
Ich habe definitiv festgestellt, dass ich jetzt beim Testen von asynchronem Code hauptsächlich solche Lösungen verwende. Es ist gut, Mocha zurückgerufen zu haben (wie in Jean Vincents Antwort oben gezeigt), aber Tests sind normalerweise einfacher zu schreiben, wenn Sie sie nicht verwenden.
Thomas Parslow
-2

Sie können auch das Domänenmodul verwenden. Zum Beispiel:

var domain = require('domain').create();

domain.run(function()
{
    // place you code here
});

domain.on('error',function(error){
    // do something with error or simply print it
});
abhishek singh bais
quelle