ฉันจะทดสอบสัญญาอย่างถูกต้องกับมอคค่าและชัยได้อย่างไร


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

หรือกับโหนดที่ทันสมัยและ async / รอคอย:

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()โทรแปลก ๆทุกที่

นี่เป็นข้อได้เปรียบที่มอคค่ามีต่อห้องสมุดอื่น ๆ เช่นจัสมินในขณะนี้ คุณอาจต้องการตรวจสอบChai As Promisedซึ่งจะทำให้ง่ายยิ่งขึ้น (ไม่.then) แต่โดยส่วนตัวแล้วฉันชอบความชัดเจนและความเรียบง่ายของเวอร์ชั่นปัจจุบัน


4
การเริ่มต้นของ Mocha รุ่นใดในครั้งนี้ ฉันได้รับEnsure the done() callback is being called in this testข้อผิดพลาดเมื่อพยายามทำเช่นนี้กับมอคค่า 2.2.5
สกอตต์

14
@Scott ไม่ใช้doneพารามิเตอร์ในการitเลือกที่จะไม่ใช้มัน
Benjamin Gruenbaum

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

4
มีปัญหาเช่นเดียวกับสกอตต์ ฉันไม่ได้ส่งdoneพารามิเตอร์ไปยังการitโทรและยังคงเกิดขึ้น ...

43

ดังที่ได้กล่าวไว้แล้วที่นี่ Mocha รุ่นใหม่จะรับรู้คำมั่นสัญญาแล้ว แต่เนื่องจาก OP ได้ถามเฉพาะเกี่ยวกับ Chai จึงมีความเป็นธรรมที่จะชี้ให้เห็นchai-as-promisedแพคเกจที่มีไวยากรณ์ที่สะอาดสำหรับการทดสอบสัญญา:

ใช้ชัยตามสัญญา

นี่คือวิธีที่คุณสามารถใช้ชัยตามที่สัญญาว่าจะทดสอบทั้งสอง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
ศักดิ์สิทธิ์ heck ขอบคุณที่บอกฉันฉันต้องโทรChai.useไปติดมัน ฉันไม่เคยหยิบมันขึ้นมาจากเอกสารที่พวกเขามี | :(
Arcym

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) จะทำให้การทดสอบล้มเหลวโดยอัตโนมัติเมื่อสัญญาถูกปฏิเสธเพิ่มขึ้น

การทดสอบเชิงลบใช้ความพยายามในการจับความคิด อย่างไรก็ตาม: 'การร้องเรียน' เกี่ยวกับการส่งผ่านที่ไม่ต้องการเกิดขึ้นหลังจากประโยคจับ (ด้วยวิธีนี้มันไม่ได้จบลงในประโยค catch () ประโยคเรียกอีกต่อไป แต่ทำให้เกิดข้อผิดพลาดที่ทำให้เข้าใจผิด

เพื่อให้กลยุทธ์นี้ใช้งานได้เราจะต้องส่งคืนการทดสอบจาก catch clause หากคุณไม่ต้องการทดสอบสิ่งอื่นให้ใช้อีกอัน () - บล็อก


2

นั่นเป็นทางออกที่ดีกว่า เพียงแค่ส่งคืนข้อผิดพลาดด้วยการทำในบล็อก 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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.