Mocha: Asynchroner Test schlägt mit Timeout statt Assertionsfehler fehl

Erstellt am 5. Feb. 2014  ·  25Kommentare  ·  Quelle: mochajs/mocha

Dieser Test:

    it('returns the correct value', function(done) {
      var returnValue = 5;

      aPromise.then(function() {
        expect(returnValue).to.equal(42);
        done();
      });

    });

Dieser Test schlägt mit timeout of 2000ms exceeded anstelle eines Assertionsfehlers fehl. Ich denke, das liegt daran, dass der Aufruf von expect() einen Fehler auslöst und done() nie ausgeführt wird, und ich frage mich, ob es eine bessere Möglichkeit gibt, diese Art von Code zu testen.

Hilfreichster Kommentar

Ich bin auf ein ähnliches Problem gestoßen und habe schließlich festgestellt, dass Sie beim Testen asynchroner Funktionen mit Versprechen nicht done verwenden sollten, sondern einfach das Versprechen zurückgeben. Sie sollten dies also ohne Timeout tun können, z.

it('returns the correct value', function() {
    var returnValue = 5;

    return aPromise.then(function() {
        expect(returnValue).to.equal(42);
    });
});

Alle 25 Kommentare

Wird aPromise jemals aufgelöst? Wenn nicht, gibt es keine andere Wahl, als eine Auszeit zu werfen.

@NickHeiner Ja, es wird aufgelöst; und dann findet expect() dass returnValue nicht equal(42) und wirft.

@gurdiga woher wissen Sie, dass es

@hallas @NickHeiner Hier ist das laufende Ding: http://jsfiddle.net/gurdiga/p9vmj/.

@gurdiga es scheint mir, dass dein Versprechen seinen eigenen Fehler hat. Versuchen Sie, Ihrem Versprechen ein .catch(done) hinzuzufügen, und ich denke, es wird wie beabsichtigt funktionieren.

@hallas Wow: _das_ war die Antwort! :) aPromise.finally() scheint die perfekte Lösung für done zu sein: es muss auch aufgerufen werden, wenn das Versprechen gelöst ist. ;)

Dankeschön!

Ich fühle mich dumm.

Ich glaube, ich habe es endlich verstanden: Wenn irgendetwas eine der Handlerfunktionen des Promises einwirft, sei es die an .then() , an .catch() oder an .finally() , ist der Fehler von der Promise-Bibliothek verarbeitet. Auf diese Weise sieht der Testläufer nie den wirklichen Fehler, done() wird nie aufgerufen (weil etwas, bevor es einen Assertion-Fehler ausgelöst hat), und somit ist der Zeitüberschreitungsfehler alles, was Sie vom Testläufer erhalten.

Um aus dem Versprechen herauszukommen, verwende ich setTimeout() :

    it('returns the correct value', function(done) {
      var returnValue = 5;

      aPromise.then(function() {
        setTimeout(function() {
          expect(returnValue).to.equal(42);
          done();
        });
      });
    });

Ich habe festgestellt, dass dies der einzige Weg ist, um richtige Fehlermeldungen zu erhalten und das Verhalten des Läufers zu testen.

Wenn done an .catch() oder .finally() der Test in jedem Fall als bestanden. Wenn also Assertion-Fehler auftreten, werden Sie diese nie sehen.

Ich bin auf ein ähnliches Problem gestoßen und habe schließlich festgestellt, dass Sie beim Testen asynchroner Funktionen mit Versprechen nicht done verwenden sollten, sondern einfach das Versprechen zurückgeben. Sie sollten dies also ohne Timeout tun können, z.

it('returns the correct value', function() {
    var returnValue = 5;

    return aPromise.then(function() {
        expect(returnValue).to.equal(42);
    });
});

Ich denke, dass dieses Problem immer noch besteht. Ich erhalte ein Zeitüberschreitungsproblem, das nicht durch Zurückgeben einer Zusage behoben werden kann, da mein Modul keine Zusagen verwendet.

it("works", function(done) {
    new Something()
    .on("eventA", function(result) {
        expect(result).to.be.true;
    })
    .on("eventB", function(result) {
        expect(result).to.be.false;
        done();
    });
});
  • Das Umschließen der Instanz in ein Promise scheint übertrieben.
  • Jede Assertion in ein try / catch einzupacken, erscheint ebenfalls übertrieben und führt, was noch wichtiger ist, zu Error: done() called multiple times .

Ideen:
http://staxmanade.com/2015/11/testing-asyncronous-code-with-mochajs-and-es7-async-await/

Was die Empfehlungen des Blog-Posts betrifft, so weiß ich nichts über die Fehlerbehandlung bei asynchronen Funktionen, aber bei einfachen Versprechungen ist die Try-Catch-Empfehlung seltsam: Der ursprüngliche Promise-Versuch ohne Try-Catch war fast korrekt, es musste nur .catch(done) anstatt den zweiten Parameter für then als done . (Zugegeben, da Mocha direkte Unterstützung für Versprechen hat, können Sie das Versprechen auch einfach zurückgeben, aber was es wert ist ...) Das Problem im ersten Versprechen-Beispiel war nicht das Fehlen von Try-Catch, sondern der zweite Handler to then wird nicht mit Ausnahmen aufgerufen, die vom ersten Handler geworfen werden, während ein folgendes catch ; Ich weiß nicht, was der Grund dafür war, dass Versprechen so gestaltet wurden, aber Versprechen sind so. Wenn es aus irgendeinem Grund mehrere then s gegeben hätte, wäre außerdem nur ein einziges letztes .catch(done) erforderlich, was gegenüber Try-Catch innerhalb der Handler (zusätzlich zu den Tatsache, dass .catch(done) vornherein weniger Boilerplatey ist).

Was Ihre API angeht:

  1. Sind Sie sicher, dass beide Ereignisse aufgerufen werden und in der richtigen Reihenfolge? Wenn dies nicht der Fall wäre, wie würde Ihr Test dies zu einem normalen Fehler machen?
  2. Was passiert normalerweise mit Ausnahmen, die von Ihren Ereignishandlern ausgelöst werden? Wenn sie sich nicht so verbreiten, wie sie es in synchronem Code tun, sondern dafür gedacht sind, auf der API zu hören (zB mit .on("error", function(error) {...}) ), dann erreichen sie Mocha nie, es sei denn, Sie hören auf sie und haben die Listener rufen done mit dem Fehler auf (oder verwenden Sie einfach done mit dem Listener, wenn dem Listener der Fehler als erster Parameter übergeben wird, zB .on("error", done) . Vermutlich wäre das auch nur müssen einmal pro Test und nicht einmal pro Event-Handler geschrieben werden, wie .catch(done) in einem Promise.
  1. Ja, und ich verwende ein "end" / "drain" Ereignis, um zu überprüfen, ob Boolesche Werte in den anderen Ereignissen gesetzt wurden.
  2. Die Zeitüberschreitung tritt ein. Ich versuche eine schlanke und saubere Alternative zu finden.

Tut mir leid, ich weiß immer noch nicht, wie Ihre API Fehler melden soll.

Ja, bisher habe ich mich nur auf die Timeouts verlassen, um _wenn_ etwas fehlschlägt, und dann manuell zu graben, um herauszufinden, _wie/warum_. Glücklicherweise gehen Dinge selten kaputt, aber das ist keine Entschuldigung für das Fehlen eines besseren Designs (meistens meinerseits).

@stevenvachon : Verzeihen Sie mir im Voraus, aber ich sehe kein unmittelbares Problem mit Ihrem Beispiel. Die in Ihren Event-Listenern gemachten Assertionen sollten von Mocha über das uncaughtException Mapping behandelt werden (es sei denn, die Event-Emitter-Implementierung fängt Listener-Fehler ab und gibt ein error Ereignis oder so aus, was dann immer noch einfach ist lösen).

Wenn Ihre Implementierung unter der Haube Versprechen verwendet, aber Ereignisse ausgibt, anstatt das Versprechen offenzulegen, werden Ihre Behauptungen tatsächlich "gefressen". Um dieses Problem zu umgehen, verwende ich unhandledRejection .

Normalerweise füge ich dies in ein Setup-Skript ein, das vor meinen Tests ausgeführt wird:

process.on('unhandledRejection', function (reason)
{
    throw reason;
});

Hinweis: Dies erfordert möglicherweise etwas zusätzliches Ellbogenfett, um in Browsern zu funktionieren.

Ich hoffe, dass Mocha dies wie uncaughtException da dies ein häufiger Anwendungsfall ist; Nur weil ich Promises verwende, heißt das nicht, dass ich sie an den Anrufer zurückgeben möchte!

Habe das gleiche Problem mit

    it('Convert files into base64', (resolve) => {
        let files = Promise.all(promises);

        return files
            .then(([actual, expected]) => {
                assert.equal(actual, expected, 'Files not are equal');
                resolve();
            })
            .catch(error => resolve);
    });
   Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.

Das .catch ist falsch. error => resolve entspricht function(error) { return resolve } , was bedeutet, dass resolve nicht aufgerufen und der Fehler ignoriert wird. Was Sie wollen, ist resolve mit dem Fehler aufzurufen, der error => resolve(error) . Natürlich ist die Übergabe einer Callback-Funktion X , die einfach die Funktion Y mit denselben Argumenten aufruft, mit denen X aufgerufen wird, gleichbedeutend mit der Übergabe von Y als Rückruf, sodass sogar .catch(error => resolve(error)) zu .catch(resolve) vereinfacht werden könnte. (Sie müssten resolve nur dann nicht direkt übergeben, wenn Sie es an then und daher vermeiden müssten, den Ergebnisparameter von then an resolve zu verhindern, dass es als Fehler behandelt wird: then(()=>resolve()) anstatt nur .then(resolve) ; aber da Sie den then Callback für die Assertionen verwenden, kommt dies nicht vor .)

(Außerdem sollte resolve hier idiomatisch ähnlich wie done , da es sowohl Erfolg als auch Misserfolg behandelt und beurteilt, was davon abhängt, ob es mit einem Argument aufgerufen wurde oder nicht. Daher der Name in Mochas Fehlermeldung. Aber das kann ein strittiger Punkt sein; lesen Sie weiter.)

In diesem Fall können Sie es jedoch noch weiter vereinfachen, indem Sie einfach das Promise zurückgeben und den Parameter test done überhaupt nicht verwenden, da Mocha darauf wartet, dass das Promise erfolgreich ist oder fehlschlägt (vorausgesetzt, es gibt keinen done-Parameter für die Testfunktion; das Verhalten, falls beide verwendet werden, wird noch ausgehasht):

it('Convert files into base64', () => {
    let files = Promise.all(promises);
    return files
        .then(([actual, expected]) => {
            assert.equal(actual, expected, 'Files not are equal');
        })
});

@lsphillips das funktioniert bei mir. Vielen Dank!! Ich hoffe, dass Mokka dies auch standardmäßig unterstützt. Ich habe gerade #2640 erstellt.

Ich habe eine Weile gebraucht, um das herauszufinden! Basierend auf den obigen Antworten sind dies die beiden Optionen:

npm install --save mocha expect.js q
./node_modules/mocha/bin/mocha test.spec.js

// test.spec.js

var $q = require('q');
var expect = require('expect.js');

describe('tests with done', function(){
    it('returns the correct value from promise', function(done) {
      var returnValue = 5;
      var def = $q.defer();
      def.promise.then((val) => {
        expect(val).to.equal(42);
        done();
      }).catch(done);
      def.resolve(returnValue)
    });
})

describe('tests returning promises', function(){
    it('returns the correct value from promise', function() {
      var returnValue = 5;
      var def = $q.defer();
      def.resolve(returnValue)
      return def.promise.then((val) => {
        expect(val).to.equal(42);
      });
    });
})
  tests with done
    1) returns the correct value from promise

  tests returning promises
    2) returns the correct value from promise


  0 passing (15ms)
  2 failing

  1) tests with done returns the correct value from promise:
     Error: expected 5 to equal 42
      at Assertion.assert (node_modules/expect.js/index.js:96:13)
      at Assertion.be.Assertion.equal (node_modules/expect.js/index.js:216:10)
      at def.promise.then (tests/test.spec.js:9:24)
      at _fulfilled (node_modules/q/q.js:854:54)
      at self.promiseDispatch.done (node_modules/q/q.js:883:30)
      at Promise.promise.promiseDispatch (node_modules/q/q.js:816:13)
      at node_modules/q/q.js:570:49
      at runSingle (node_modules/q/q.js:137:13)
      at flush (node_modules/q/q.js:125:13)
      at _combinedTickCallback (internal/process/next_tick.js:67:7)
      at process._tickCallback (internal/process/next_tick.js:98:9)

  2) tests returning promises returns the correct value from promise:
     Error: expected 5 to equal 42
      at Assertion.assert (node_modules/expect.js/index.js:96:13)
      at Assertion.be.Assertion.equal (node_modules/expect.js/index.js:216:10)
      at def.promise.then (tests/test.spec.js:22:24)
      at _fulfilled (node_modules/q/q.js:854:54)
      at self.promiseDispatch.done (node_modules/q/q.js:883:30)
      at Promise.promise.promiseDispatch (node_modules/q/q.js:816:13)
      at node_modules/q/q.js:570:49
      at runSingle (node_modules/q/q.js:137:13)
      at flush (node_modules/q/q.js:125:13)
      at _combinedTickCallback (internal/process/next_tick.js:67:7)
      at process._tickCallback (internal/process/next_tick.js:98:9)

@gurdiga Danke für die setTimeout() Idee! Ich hatte ein ähnliches Problem, aber jetzt bekomme ich zumindest richtige Fehlermeldungen!

In meinem Szenario habe ich Nightmare verwendet, um 2end-Tests zu beenden. Die Lösung für mich war .catch(done) . Sie können done(error) innerhalb eines anderen Catch-Callbacks aufrufen, wie im folgenden Beispiel.

describe('Clicking in any bad reputation tag', () => {
    it('open the bad reputation modal', (done) => {
      nightmare
        .select('#per-page', '50')
        .waitForAjax()
        .click('[data-reputation="bad"]')
        .evaluate(function() {
          return document.querySelector('.vue-modal .ls-modal-title').innerText
        })
        .then(function(title) {
          title.should.equal('Sua segmentação teve uma avaliação ruim!')
          done()
        })
        .catch((error) => {
          screenshot(nightmare)
          done(error)
        })
    })
  })

@itumoraes , das funktioniert, aber Sie können stattdessen

describe('Clicking in any bad reputation tag', () => {
    it('open the bad reputation modal', () => {
      return nightmare
        .select('#per-page', '50')
        .waitForAjax()
        .click('[data-reputation="bad"]')
        .evaluate(function() {
          return document.querySelector('.vue-modal .ls-modal-title').innerText
        })
        .then(function(title) {
          title.should.equal('Sua segmentação teve uma avaliação ruim!')
        })
    })
  })

Sie müssen done() nicht anrufen, wenn Sie ein Versprechen zurückgeben. Siehe meinen Blogbeitrag Using Async/Await with Mocha, Express und Mongoose

Ich habe das folgende Skript verwendet, aber ich habe den gleichen Timeout-Überschreitungsfehler erhalten.

Mein Skript:

describe("getBillingDetail", asynchrone Funktion (){
this.timeout(55000);
it.only("Überprüfe einen gültigen Jobnamen",async-Funktion (erledigt){
this.timeout(55000);
var result = wait url.getBillingDetail('12254785565647858');
Konsole.log (Ergebnis);
behaupten.equal(Ergebnis,wahr);
});
});

Fehler: Timeout von 55000ms überschritten. Stellen Sie bei asynchronen Tests und Hooks sicher, dass "done()" aufgerufen wird; Wenn Sie ein Versprechen zurückgeben, stellen Sie sicher, dass es aufgelöst wird.

Hören Sie auf, in mehreren geschlossenen Ausgaben dasselbe zu buchstabieren. Übergeben Sie keinen abgeschlossenen Rückruf an asynchrone Funktionen. Lesen Sie die Dokumentation zu asynchronen Tests

@Munter Ich habe den abgeschlossenen Rückruf entfernt, aber dieser Fehler tritt erneut auf

Es sieht so aus, als ob Ihr Versprechen nie gelöst wurde.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen