Bagaimana cara menguji janji dengan benar dengan moka dan chai?

148

Tes berikut berperilaku aneh:

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

Bagaimana saya harus benar menangani janji yang ditolak (dan mengujinya)?

Bagaimana saya harus benar menangani tes gagal (yaitu: expect(data.rate).to.have.length(400);?

Berikut ini adalah implementasi yang saya uji:

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

Jawaban:

233

Hal termudah untuk dilakukan adalah menggunakan dukungan janji bawaan Mocha dalam versi terbaru:

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

Atau dengan Node modern dan async / tunggu:

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

Karena pendekatan ini menjanjikan ujung ke ujung, lebih mudah untuk menguji dan Anda tidak perlu memikirkan kasus aneh yang Anda pikirkan seperti done()panggilan aneh di mana-mana.

Ini adalah keuntungan yang dimiliki Mocha dibandingkan perpustakaan lain seperti Jasmine saat ini. Anda mungkin juga ingin memeriksa Chai As Promised yang akan membuatnya lebih mudah (tidak .then) tetapi secara pribadi saya lebih suka kejelasan dan kesederhanaan dari versi saat ini

Benjamin Gruenbaum
sumber
4
Dalam versi apa Mocha memulai ini? Saya mendapatkan Ensure the done() callback is being called in this testkesalahan ketika mencoba melakukan ini dengan mocha 2.2.5.
Scott
14
@Scott tidak mengambil doneparameter di ityang akan memilih keluar dari itu.
Benjamin Gruenbaum
2
Ini sangat membantu saya. Menghapus donedalam itcallback saya , dan secara eksplisit memanggil return(pada janji) di callback adalah cara saya membuatnya berfungsi, seperti dalam cuplikan kode.
JohnnyCoder
5
Jawaban yang luar biasa, bekerja dengan sempurna. Melihat kembali pada dokumen, itu ada di sana - mudah untuk dilewatkan kurasa. 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
4
Memiliki masalah yang sama dengan Scott. Saya tidak meneruskan doneparameter ke itpanggilan, dan ini masih terjadi ...
43

Seperti yang sudah ditunjukkan di sini , versi Mocha yang lebih baru sudah sadar-Janji. Tapi karena OP bertanya secara spesifik tentang Chai, itu adil untuk menunjukkan chai-as-promisedpaket yang menyediakan sintaksis bersih untuk menguji janji:

menggunakan chai-seperti yang dijanjikan

Inilah cara Anda dapat menggunakan chai-as-berjanji untuk menguji keduanya resolvedan rejectkasus untuk Janji:

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

tanpa chai seperti yang dijanjikan

Untuk memperjelas apa yang sedang diuji, berikut ini contoh kode yang sama tanpa chai-as-janjikan:

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
sumber
5
Masalah dengan pendekatan kedua adalah yang catchdipanggil ketika salah satu expect(s)gagal. Ini memberi kesan yang salah bahwa janji gagal meskipun tidak. Hanya harapan yang gagal.
TheCrazyProgrammer
2
Holy heck, terima kasih sudah memberitahuku bahwa aku harus menelepon Chai.useuntuk memasangnya. Saya tidak akan pernah mengambilnya dari dokumentasi yang mereka miliki. | :(
Arcym
3

Inilah pendapat saya:

  • menggunakan async/await
  • tidak membutuhkan modul chai tambahan
  • menghindari masalah tangkapan, @TheCrazyProgrammer menunjukkan di atas

Fungsi janji yang tertunda, yang gagal, jika diberi penundaan 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')
})

Tes positif agak sederhana. Kegagalan yang tidak terduga (disimulasikan oleh 500→0) akan gagal dalam tes secara otomatis, karena janji yang ditolak meningkat.

Tes negatif menggunakan ide coba-tangkap. Namun: 'mengeluh' tentang umpan yang tidak diinginkan hanya terjadi setelah klausa tangkap (dengan demikian, tidak berakhir pada klausa tangkap (), memicu kesalahan lebih lanjut tetapi menyesatkan.

Agar strategi ini berhasil, seseorang harus mengembalikan tes dari klausa tangkapan. Jika Anda tidak ingin menguji hal lain, gunakan yang lain () - blokir.

Frank Nocke
sumber
2

Ada solusi yang lebih baik. Kembalikan kesalahan dengan dilakukan di blok tangkap.

// ...

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

tes ini akan gagal dengan pesan berikut: AssertionError: expected 1 to equal 11

di3
sumber