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

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

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

3 ответа

Решение

Проще всего было бы использовать встроенную поддержку обещаний, которую 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) но лично я предпочитаю четкость и простоту текущей версии

Как уже указывалось здесь, более новые версии 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'); })
            ;
});

Вот мое мнение:

  • с помощью 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().

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

Другие вопросы по тегам