Як я правильно перевіряю обіцянки моккою та чаєм?


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

Відповіді:


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 та асинхронізуванням / очікуванням:

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 має в порівнянні з іншими бібліотеками, як Жасмін на даний момент. Ви також можете перевірити Chai As Promised, що зробило б це ще простіше (ні .then), але особисто я віддаю перевагу ясність та простоту поточної версії


4
У якій версії Mocha це почалося? Я отримую Ensure the done() callback is being called in this testпомилку при спробі зробити це за допомогою mocha 2.2.5.
Скотт

14
@Scott не приймайте 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 вже обізнані. Але оскільки ОП запитала конкретно про Чая, справедливо лише зазначити chai-as-promisedпакет, який містить чистий синтаксис для тестування обіцянок:

використовуючи як-то обіцяне

Ось як ви можете використовувати chai, як і було обіцяно, для тестування обох 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');
});

без того, як обіцяли

Щоб зрозуміти, що тестується, ось той самий приклад, кодований без того, як обіцяно:

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

5
Проблема з другим підходом полягає в тому, що catchвикликається, коли один з expect(s)ладів. Це створює неправильне враження, що обіцянка не відбулася, хоча вона і не відбулася. Лише очікувати не вдалося.
TheCrazyProgrammer

2
Дякую, дякую за те, що ти сказав мені, що я повинен закликати Chai.useїї встановити Я б ніколи не брав цього з документації, яку вони мали. | :(
Арцим

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) випробує тест автоматично, оскільки відхилена обіцянка посилюється.

Негативний тест використовує ідею спробу вловлювання. Однак: "скарги" на небажаний пропуск трапляються лише після застереження про вилов (таким чином, він не закінчується в пункті catch (), викликаючи подальші, але оманливі помилки.

Щоб ця стратегія спрацювала, потрібно повернути тест із застереження. Якщо ви не хочете тестувати нічого іншого, скористайтеся іншим блоком it ().


2

Thre - краще рішення. Просто поверніть помилку з зробленим у блоці улову.

// ...

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.