モカとチャイでプロミスを適切にテストするにはどうすればよいですか?


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が最近のバージョンで持っている組み込みの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
});

または最新のノードと非同期/待機:

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 testmocha 2.2.5でこれを実行しようとすると、エラーが発生します。
スコット

14
@スコットはそれからオプトアウトするであろうdoneパラメータを取りませんit
Benjamin Gruenbaum 2015年

2
これはとても役に立ちました。コールバックでを削除doneし、itコールバックで明示的に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:
フェデリコ2016年

4
スコットと同じ問題がある。呼び出しにdoneパラメーターを渡していないのですがit、これはまだ発生しています...

43

ここですでに指摘したように、Mochaの新しいバージョンはすでにPromise対応です。しかし、OPは特にChaiについて尋ねたので、promise chai-as-promisedをテストするための明確な構文を提供するパッケージを指摘するのは公正です。

約束通りのチャイを使う

chai-as-promisedを使用して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なしでコーディングされた同じ例を次に示します。

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
2番目のアプローチの問題catchは、いずれかがexpect(s)失敗したときに呼び出されることです。これは約束が失敗しなかったとしても失敗したという誤った印象を与えます。失敗したのは期待だけです。
TheCrazyProgrammer 2017年

2
私はChai.useそれをマウントするために電話しなければならないことを私に言ってくれて本当にありがとう。私は彼らが持っていたドキュメントからそれを取り上げたことはなかったでしょう。| :(
Arcym 2017年

3

これが私の見解です。

  • を使用して async/await
  • 追加のチャイモジュールは必要ありません
  • キャッチの問題を回避するために、@ 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

Threはより良いソリューションです。catchブロックでdoneを使用してエラーを返すだけです。

// ...

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.