Как правильно проверить обещания с мокко и чай?

148

Следующий тест ведет себя странно:

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

Как правильно обработать отклоненное обещание (и проверить его)?

Как правильно обрабатывать неудачный тест (например expect(data.rate).to.have.length(400);:?

Вот реализация, которую я тестирую:

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
источник

Ответы:

233

Проще всего было бы использовать встроенную поддержку обещаний, которую Mocha имеет в последних версиях:

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

Или с современным Node и async / await:

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

Поскольку этот подход представляет собой обещание от начала до конца, его легче протестировать, и вам не придется думать о странных случаях, о которых вы думаете, как о странных done()вызовах повсюду.

Это преимущество Mocha перед другими библиотеками, такими как Jasmine на данный момент. Возможно, вы также захотите проверить Chai As Promised, что сделает это еще проще (нет .then), но лично я предпочитаю ясность и простоту текущей версии

Бенджамин Грюнбаум
источник
4
В какой версии Мокко это началось? Я получаю сообщение Ensure the done() callback is being called in this testоб ошибке при попытке сделать это с помощью Mocha 2.2.5.
Скотт
14
@ Скотт не принимает doneпараметр, itкоторый мог бы отказаться от него.
Бенджамин Грюнбаум
2
Это было очень полезно для меня. Удаление doneв моем itобратном вызове и явный вызов return(в обещании) в обратном вызове - вот как я это сделал, как в фрагменте кода.
JohnnyCoder
5
Отличный ответ, отлично работает. Оглядываясь назад на документы, это там - просто легко пропустить, я думаю. 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:
Федерико
4
Возникла та же проблема, что и у Скотта. Я не doneit
43

Как уже указывалось здесь , более новые версии Mocha уже поддерживают Promise. Но так как ОП спрашивал конкретно о Чай, было бы справедливо указать на chai-as-promisedпакет, который обеспечивает чистый синтаксис для тестирования обещаний:

используя чай как обещал

Вот как вы можете использовать chai-as-обещано, чтобы проверить оба resolveи rejectдела для Обещания:

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

без чая как обещал

Чтобы прояснить, что тестируется, вот тот же пример, кодированный без chai-as-обещано:

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'); })
            ;
});
fearless_fool
источник
5
Проблема со вторым подходом заключается в том, что catchон вызывается при expect(s)сбое. Это создает неправильное впечатление, что обещание не удалось, хотя оно и не было. Это только ожидание, которое не удалось.
TheCrazyProgrammer
2
Черт побери, спасибо, что сказал мне, что я должен позвонить, Chai.useчтобы смонтировать его Я бы никогда не узнал об этом из документации, которую они имели. | :(
Arcym
3

Вот мой дубль:

  • с помощью async/await
  • не требуются дополнительные модули Chai
  • @TheCrazyProgrammer указывал выше, чтобы избежать проблемы с уловом

Функция отложенного обещания, которая завершается ошибкой, если задана задержка 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')
})

Положительный тест довольно прост. Неожиданный сбой (имитировать 500→0) автоматически провалит тест, так как отклоненное обещание возрастает.

Отрицательный тест использует try-catch-idea. Однако «жалоба» на нежелательный проход происходит только после предложения catch (таким образом, он не заканчивается в предложении catch (), вызывая дальнейшие, но вводящие в заблуждение ошибки.

Чтобы эта стратегия работала, необходимо вернуть тест из предложения catch. Если вы не хотите ничего тестировать, используйте другой блок it ().

Фрэнк Нок
источник
2

Это лучшее решение. Просто верните ошибку с done в блоке 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
  }); 
});

этот тест не пройдёт со следующим сообщением: AssertionError: expected 1 to equal 11

di3
источник