我如何正确地用摩卡咖啡和柴测试诺言?


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

Answers:


233

最简单的方法是使用Mocha在最新版本中提供的内置Promise支持:

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
这是从哪个版本的Mocha开始的?Ensure the done() callback is being called in this test尝试在Mocha 2.2.5中执行此操作时出现错误。
斯科特

14
@Scott不要doneit中选择退出的参数。
本杰明·格林鲍姆

2
这对我很有帮助。就像在代码片段中一样,删除回调中的,并doneit回调中显式调用return(按Promise)是它的工作方式。
JohnnyCoder 2015年

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:
Federico'1

4
与斯科特有同样的问题。我没有将done参数传递给it调用,并且这种情况仍在发生...

43

正如此处已经指出的那样,Mocha的较新版本已经支持Promise。但是由于OP专门询问了Chai,因此可以公平地指出该chai-as-promised软件包提供了用于测试诺言的干净语法:

使用柴如许的

这是您如何使用chai-as-promise测试Promise resolvereject案例的方法:

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-promise的情况下编码的同一示例:

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来安装它。我永远不会从他们的文档中挑选出来。| :(
Arcym

3

这是我的看法:

  • 使用 async/await
  • 不需要额外的柴模块
  • 避免捕获问题,@ TheCrazyProgrammer在上面指出

如果给定延迟为0,则延迟的promise函数将失败:

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

Thre是更好的解决方案。只需在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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.