Wie teste ich Versprechen richtig mit Mokka und Chai?

148

Der folgende Test verhält sich merkwürdig:

it('Should return the exchange rates for btc_ltc', function(done) {
    var pair = 'btc_ltc';

    shapeshift.getRate(pair)
        .then(function(data){
            expect(data.pair).to.equal(pair);
            expect(data.rate).to.have.length(400);
            done();
        })
        .catch(function(err){
            //this should really be `.catch` for a failed request, but
            //instead it looks like chai is picking this up when a test fails
            done(err);
        })
});

Wie soll ich mit einem abgelehnten Versprechen richtig umgehen (und es testen)?

Wie soll ich mit einem fehlgeschlagenen Test richtig umgehen (dh : expect(data.rate).to.have.length(400);?

Hier ist die Implementierung, die ich teste:

var requestp = require('request-promise');
var shapeshift = module.exports = {};
var url = 'http://shapeshift.io';

shapeshift.getRate = function(pair){
    return requestp({
        url: url + '/rate/' + pair,
        json: true
    });
};
chovy
quelle

Antworten:

233

Am einfachsten wäre es, die eingebauten Versprechen zu verwenden, die Mocha in den letzten Versionen unterstützt:

it('Should return the exchange rates for btc_ltc', function() { // no done
    var pair = 'btc_ltc';
    // note the return
    return shapeshift.getRate(pair).then(function(data){
        expect(data.pair).to.equal(pair);
        expect(data.rate).to.have.length(400);
    });// no catch, it'll figure it out since the promise is rejected
});

Oder mit modernem Node und Async / Warten:

it('Should return the exchange rates for btc_ltc', async () => { // no done
    const pair = 'btc_ltc';
    const data = await shapeshift.getRate(pair);
    expect(data.pair).to.equal(pair);
    expect(data.rate).to.have.length(400);
});

Da dieser Ansatz Versprechen von Ende zu Ende ist, ist es einfacher zu testen und Sie müssen nicht über die seltsamen Fälle nachdenken, an die Sie denken, wie die seltsamen done()Anrufe überall.

Dies ist ein Vorteil, den Mocha derzeit gegenüber anderen Bibliotheken wie Jasmine hat. Vielleicht möchten Sie auch Chai As Promised überprüfen, was es noch einfacher machen würde (nein .then), aber ich persönlich bevorzuge die Klarheit und Einfachheit der aktuellen Version

Benjamin Gruenbaum
quelle
4
In welcher Version von Mocha hat das angefangen? Ensure the done() callback is being called in this testBeim Versuch, dies mit Mokka 2.2.5 zu tun , wird eine Fehlermeldung angezeigt.
Scott
14
@Scott nimmt keinen doneParameter in das it, der sich dagegen entscheiden würde.
Benjamin Gruenbaum
2
Das war sehr hilfreich für mich. Durch Entfernen des donein meinem itRückruf und expliziten Aufrufs return(auf das Versprechen) im Rückruf habe ich es zum Laufen gebracht, genau wie im Code-Snippet.
JohnnyCoder
5
Super Antwort, funktioniert perfekt. Rückblickend auf die Dokumente ist es da - einfach leicht zu übersehen, denke ich. Alternately, instead of using the done() callback, you may return a Promise. This is useful if the APIs you are testing return promises instead of taking callbacks:
Federico
4
Ich habe das gleiche Problem wie Scott. Ich übergebe keinen doneParameter an den itAufruf, und dies geschieht immer noch ...
43

Wie bereits erwähnt hier sind die neueren Versionen von Mokka bereits Versprechen-aware. Da das OP jedoch speziell nach Chai gefragt hat, ist es nur fair, auf das chai-as-promisedPaket hinzuweisen, das eine saubere Syntax zum Testen von Versprechungen bietet:

mit Chai-wie versprochen

So können Sie Chai-as-versprochen verwenden, um sowohl Fälle resolveals auch rejectFälle für ein Versprechen zu testen :

var chai = require('chai');
var expect = chai.expect;
var chaiAsPromised = require("chai-as-promised");
chai.use(chaiAsPromised);

...

it('resolves as promised', function() {
    return expect(Promise.resolve('woof')).to.eventually.equal('woof');
});

it('rejects as promised', function() {
    return expect(Promise.reject('caw')).to.be.rejectedWith('caw');
});

ohne Chai-wie versprochen

Um wirklich klar zu machen, was getestet wird, hier das gleiche Beispiel, das ohne versprochenes Chai codiert wurde:

it('resolves as promised', function() {
    return Promise.resolve("woof")
        .then(function(m) { expect(m).to.equal('woof'); })
        .catch(function(m) { throw new Error('was not supposed to fail'); })
            ;
});

it('rejects as promised', function() {
    return Promise.reject("caw")
        .then(function(m) { throw new Error('was not supposed to succeed'); })
        .catch(function(m) { expect(m).to.equal('caw'); })
            ;
});
furchtloser Dummkopf
quelle
5
Das Problem mit dem zweiten Ansatz ist, dass catchaufgerufen wird, wenn einer der expect(s)Fehler auftritt. Dies erweckt den falschen Eindruck, dass das Versprechen fehlgeschlagen ist, obwohl dies nicht der Fall war. Es ist nur die Erwartung, die fehlgeschlagen ist.
TheCrazyProgrammer
2
Verdammt, danke, dass du mir gesagt hast, ich muss anrufen Chai.use, um es zu besteigen. Ich hätte das nie aus den Unterlagen aufgegriffen, die sie hatten. | :(
Arcym
3

Hier ist meine Einstellung:

  • mit async/await
  • Keine zusätzlichen Chai-Module erforderlich
  • @TheCrazyProgrammer hat das oben genannte Problem vermieden und darauf hingewiesen

Eine verzögerte Versprechensfunktion, die bei einer Verzögerung von 0 fehlschlägt:

const timeoutPromise = (time) => {
    return new Promise((resolve, reject) => {
        if (time === 0)
            reject({ 'message': 'invalid time 0' })
        setTimeout(() => resolve('done', time))
    })
}

//                     ↓ ↓ ↓
it('promise selftest', async () => {

    // positive test
    let r = await timeoutPromise(500)
    assert.equal(r, 'done')

    // negative test
    try {
        await timeoutPromise(0)
        // a failing assert here is a bad idea, since it would lead into the catch clause…
    } catch (err) {
        // optional, check for specific error (or error.type, error. message to contain …)
        assert.deepEqual(err, { 'message': 'invalid time 0' })
        return  // this is important
    }
    assert.isOk(false, 'timeOut must throw')
    log('last')
})

Positiver Test ist ziemlich einfach. Ein unerwarteter Fehler (simulieren mit 500→0) schlägt den Test automatisch fehl, da das abgelehnte Versprechen eskaliert.

Negativer Test verwendet die Try-Catch-Idee. Allerdings: "Beschweren" über einen unerwünschten Pass erfolgt erst nach der catch-Klausel (auf diese Weise endet es nicht in der catch () -Klausel und löst weitere, aber irreführende Fehler aus.

Damit diese Strategie funktioniert, muss der Test aus der catch-Klausel zurückgegeben werden. Wenn Sie nichts anderes testen möchten, verwenden Sie einen anderen it () - Block.

Frank Nocke
quelle
2

Es ist eine bessere Lösung. Geben Sie einfach den Fehler mit done in einem catch-Block zurück.

// ...

it('fail', (done) => {
  // any async call that will return a Promise 
  ajaxJson({})
  .then((req) => {
    expect(1).to.equal(11); //this will throw a error
    done(); //this will resove the test if there is no error
  }).catch((e) => {
    done(e); //this will catch the thrown error
  }); 
});

Dieser Test schlägt mit der folgenden Meldung fehl: AssertionError: expected 1 to equal 11

di3
quelle