2012-06-27 65 views
73

我在摩卡使用瀏覽器運行亞軍一些異步測試,我試圖用柴的期待風格斷言:有沒有辦法讓Chai與異步摩卡測試一起工作?

window.expect = chai.expect; 
describe('my test', function() { 
    it('should do something', function (done) { 
    setTimeout(function() { 
     expect(true).to.equal(false); 
    }, 100); 
    } 
} 

這不會給我正常的失敗斷言消息,而不是我得到:

Error: the string "Uncaught AssertionError: expected true to equal false" was thrown, throw an Error :) 
    at Runner.fail (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3475:11) 
    at Runner.uncaught (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3748:8) 
    at uncaught (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3778:10) 

所以它顯然捕捉錯誤,它只是沒有正確顯示它。任何想法如何做到這一點?我想我可以用一個錯誤對象調用「完成」,但是然後我失去了Chai之類的所有優雅,並且它變得非常笨重......

+0

問題是與瀏覽器端摩卡。有關此信息,請參閱https://github.com/visionmedia/mocha/pull/278。 –

回答

88

您的異步測試會在失敗的expect()中生成異常,但it()無法捕獲該異常,因爲該異常將被拋出it()的範圍之外。

您看到顯示的捕獲異常是在節點下使用process.on('uncaughtException')或在瀏覽器中使用window.onerror()捕獲的。

要解決此問題,您需要捕獲由setTimeout()調用的異步函數內的異常,以便將異常作爲第一個參數調用done()

window.expect = chai.expect; 

describe('my test', function() { 
    it('should do something', function (done) { 
    // done() is provided by it() to indicate asynchronous completion 
    // call done() with no parameter to indicate that it() is done() and successful 
    // or with an error to indicate that it() failed 
    setTimeout(function() { 
     // Called from the event loop, not it() 
     // So only the event loop could capture uncaught exceptions from here 
     try { 
     expect(true).to.equal(false); 
     done(); // success: call done with no parameter to indicate that it() is done() 
     } catch(e) { 
     done(e); // failure: call done with an error Object to indicate that it() failed 
     } 
    }, 100); 
    // returns immediately after setting timeout 
    // so it() can no longer catch exception happening asynchronously 
    } 
} 

在所有的測試用例這樣做是討厭和:你還需要調用done()不帶參數表示成功,否則,因爲你的測試功能絕不會暗示有人做過摩卡將報告超時錯誤不幹,所以你可能想提供一個功能爲你做這個。讓我們來調用這個函數check()

function check(done, f) { 
    try { 
    f(); 
    done(); 
    } catch(e) { 
    done(e); 
    } 
} 

隨着check()現在你可以重寫你的異步測試如下:

window.expect = chai.expect; 

describe('my test', function() { 
    it('should do something', function(done) { 
    setTimeout(function() { 
     check(done, function() { 
     expect(true).to.equal(false); 
     }); 
    }, 100); 
    } 
} 
+0

我剛剛刪除了我以前的評論後,我意識到我抱怨(setTimeout)實際上是從我的問題。抱歉!! –

+1

上面的答案看起來不對。一個失敗的期望會立即拋出,並停止測試,並帶來一個有意義的錯誤,不需要複雜的try/catch。我剛剛通過瀏覽器測試對其進行了測試。 – Offirmo

+0

它可能已在更高版本中修復,答案與兩年前在該問題中暴露的非常實際的問題有關。你有沒有試過運行答案測試代碼?您使用的是哪個版本的Mocha和Chai? –

2

我在Mocha郵件列表中詢問了同樣的事情。他們基本上告訴我:寫有摩卡和柴異步測試:

  • 總是開始測試與if (err) done(err);
  • 總是done()結束測試。

它解決了我的問題,並沒有改變我的代碼之間的單行(柴期望等)。 setTimout不是做異步測試的方法。

這是link to the discussion in the mailing list

+1

您關聯的討論是關於服務器端chai和mocha。海報正在問**瀏覽器端**摩卡和柴。 –

+0

這不是同一個問題。在這個問題中用作示例的'setTimeout'函數在其回調中沒有任何錯誤。 – Sylvain

13

如果你喜歡承諾,儘量Chai as Promised + Q,允許這樣的事情:

doSomethingAsync().should.eventually.equal("foo").notify(done); 
-2

對我來說非常好用icm Mocha/Chai是來自Sinon圖書館的假定時器。 只需在必要時進行測試中的計時器。

var sinon = require('sinon'); 
clock = sinon.useFakeTimers(); 
// Do whatever. 
clock.tick(30000); // Advances the JS clock 30 seconds. 

還有讓測試更快完成的額外好處。

+1

我在測試異步代碼時已經發現我大多使用這樣的解決方案。 「完成」回調Mocha是很好的(正如Jean Vincent的答案中所示),但是當你不使用測試時,測試通常更容易編寫。 –

1

我已經發布了一個解決此問題的包。

首先安裝check-chai包:

npm install --save check-chai

然後在您的測試中,使用chai.use(checkChai);然後使用chai.check輔助功能,如下圖所示:

var chai = require('chai'); 
var dirtyChai = require('dirty-chai'); 
var checkChai = require('check-chai'); 
var expect = chai.expect; 
chai.use(dirtyChai); 
chai.use(checkChai); 

describe('test', function() { 

    it('should do something', function(done) { 

    // imagine you have some API call here 
    // and it returns (err, res, body) 
    var err = null; 
    var res = {}; 
    var body = {}; 

    chai.check(done, function() { 
     expect(err).to.be.a('null'); 
     expect(res).to.be.an('object'); 
     expect(body).to.be.an('object'); 
    }); 

    }); 

}); 

Is there a way to get Chai working with asynchronous Mocha tests?我出版這是一個NPM包。

欲瞭解更多信息,請參閱https://github.com/niftylettuce/check-chai

12

這裏是我通過的測試ES6/ES2015承諾和ES7/ES2016異步/等待。希望這提供了一個很好的答案更新任何人研究這個話題:

import { expect } from 'chai' 

describe('Mocha',() => { 
    it('works synchronously',() => { 
    expect(true).to.equal(true) 
    }) 

    it('works ansyncronously', done => { 
    setTimeout(() => { 
     expect(true).to.equal(true) 
     done() 
    }, 4) 
    }) 

    it('throws errors synchronously',() => { 
    return true 
    throw new Error('it works') 
    }) 

    it('throws errors ansyncronously', done => { 
    setTimeout(() => { 
     return done() 
     done(new Error('it works')) 
    }, 4) 
    }) 

    it('uses promises',() => { 
    var testPromise = new Promise((resolve, reject) => { 
     setTimeout(() => { 
     resolve('Hello') 
     }, 4) 
    }) 

    testPromise.then(result => { 
     expect(result).to.equal('Hello') 
    }, reason => { 
     throw new Error(reason) 
    }) 
    }) 

    it('uses es7 async/await', async (done) => { 
    const testPromise = new Promise((resolve, reject) => { 
     setTimeout(() => { 
     resolve('Hello') 
     }, 4) 
    }) 

    try { 
     const result = await testPromise 
     expect(result).to.equal('Hello') 
     done() 
    } catch(err) { 
     done(err) 
    } 
    }) 

    /* 
    * Higher-order function for use with async/await (last test) 
    */ 
    const mochaAsync = fn => { 
    return async (done) => { 
     try { 
     await fn() 
     done() 
     } catch (err) { 
     done(err) 
     } 
    } 
    } 

    it('uses a higher order function wrap around async', mochaAsync(async() => { 
    const testPromise = new Promise((resolve, reject) => { 
     setTimeout(() => { 
     resolve('Hello') 
     }, 4) 
    }) 

    expect(await testPromise).to.equal('Hello') 
    })) 
}) 
+0

@Pedro R.我改變了刪除諾言測試。正如你所指出的那樣,這不是必需的。 – RichardForrester

0

我解決它提取try/catch的功能。

function asyncExpect(test, done){ 
    try{ 
     test(); 
     done(); 
    } catch(error){ 
     done(error); 
    } 
} 

然後在it()我打電話:

it('shall update a host', function (done) { 
      testee.insertHost({_id: 'host_id'}) 
       .then(response => { 
        asyncExpect(() => { 
         expect(response).to.have.property('ok', 1); 
         expect(response).to.have.property('nModified', 1); 
        }, done); 
       }); 

     }); 

它也可調試。

0

基於@richardforrester http://staxmanade.com/2015/11/testing-asyncronous-code-with-mochajs-and-es7-async-await/提供的鏈接,如果您省略done參數,describe可以使用返回的Promise。

唯一的缺點是必須有Promise,沒有任何異步功能(你可以用Promise包裝它)。但在這種情況下,代碼可以大大減少。

它考慮到失敗從無論是在初始funcThatReturnsAPromise功能或期望:

it('should test Promises', function() { // <= done removed 
    return testee.funcThatReturnsAPromise({'name': 'value'}) // <= return added 
     .then(response => expect(response).to.have.property('ok', 1)); 
}); 
-2

您還可以使用域模塊。例如:

var domain = require('domain').create(); 

domain.run(function() 
{ 
    // place you code here 
}); 

domain.on('error',function(error){ 
    // do something with error or simply print it 
}); 
1

有很大關係,並通過Jean Vincent's answer的啓發,我們採用類似於他check功能的輔助函數,但是我們把它叫做eventually,而不是(這有助於它匹配了柴原樣的命名約定承諾)。它返回一個函數,它接受任意數量的參數並將它們傳遞給原始回調函數。這有助於消除測試中的額外嵌套功能塊,並允許您處理任何類型的異步回調。這是寫在ES2015:

function eventually(done, fn) { 
    return (...args) => { 
    try { 
     fn(...args); 
     done(); 
    } catch (err) { 
     done(err); 
    } 
    }; 
}; 

實例應用:

describe("my async test", function() { 
    it("should fail", function(done) { 
    setTimeout(eventually(done, (param1, param2) => { 
     assert.equal(param1, "foo"); // this should pass 
     assert.equal(param2, "bogus"); // this should fail 
    }), 100, "foo", "bar"); 
    }); 
}); 
相關問題