web-dev-qa-db-fra.com

Comment tester correctement les promesses avec le moka et le chai?

Le test suivant se comporte étrangement:

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

Comment dois-je gérer correctement une promesse refusée (et la tester)?

Comment gérer correctement un test qui a échoué (c.-à-d.: expect(data.rate).to.have.length(400);?

Voici l'implémentation que je 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
    });
};
138
chovy

La chose la plus simple à faire serait d'utiliser les promesses intégrées au support de Mocha dans les versions récentes:

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

Ou avec Node moderne et asynchrone/wait:

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

Comme cette approche est une promesse de bout en bout, il est plus facile de la tester et vous n’aurez pas à penser aux cas étranges auxquels vous pensez, comme aux appels done() impairs, partout.

C'est un avantage que Mocha a par rapport aux autres bibliothèques comme Jasmine pour le moment. Vous voudrez peut-être aussi vérifier Chai Comme Promis , ce qui le rendrait encore plus facile (pas de .then), mais personnellement, je préfère la clarté et la simplicité de la version actuelle.

217
Benjamin Gruenbaum

Comme déjà indiqué ici , les nouvelles versions de Mocha sont déjà compatibles avec Promise. Mais depuis que l'OP a spécifiquement posé des questions sur Chai, il est juste de signaler le paquetage chai-as-promised qui fournit une syntaxe propre pour tester les promesses:

en utilisant le chai comme promis

Voici comment utiliser chai-as-promise pour tester les deux cas resolve et reject d'une promesse:

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

sans chai comme promis

Pour bien comprendre ce qui va être testé, voici le même exemple codé sans chai comme promis:

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'); })
            ;
});
39
fearless_fool

Tehre est une meilleure solution. Il suffit de renvoyer l'erreur with done dans un bloc catch.

// ...

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

ce test échouera avec le message suivant: AssertionError: expected 1 to equal 11

2
di3

Voici ma prise:

  • en utilisant async/await
  • pas besoin de modules chai supplémentaires
  • éviter le problème des prises, @TheCrazyProgrammer a souligné ci-dessus

Fonction de promesse retardée, qui échoue si on lui donne un retard de 0:

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')
})

test positif est assez simple. Un échec inattendu (simulé par 500→0) échouera automatiquement au test, à mesure que la promesse rejetée dégénère.

test négatif utilise le principe try-catch-idea. Cependant: "se plaindre" à propos d'une passe indésirable ne se produit qu'après la clause catch (ainsi, cela ne se retrouvera pas dans la clause catch (), ce qui déclenchera des erreurs supplémentaires mais trompeuses.

Pour que cette stratégie fonctionne, il faut renvoyer le test à partir de la clause catch. Si vous ne voulez rien tester d'autre, utilisez un autre bloc it () - block.

2
Frank Nocke