在這里,我們將會學習什么是Mocha支持的“對Promise測試”。
官方網站?[Asynchronous code](http://mochajs.org/#asynchronous-code)?也記載了關于Promise測試的概要。
>
> Alternately, instead of using the done() callback, you can return a promise. This is useful if the APIs you are testing return promises instead of taking callbacks:
>
這段話的意思是,在對Promise進行測試的時候,不使用?`done()`?這樣的回調風格的代碼編寫方式,而是返回一個promise對象。
那么實際上代碼將會是什么樣的呢?這里我們來看個具體的例子應該容易理解了。
mocha-promise-test.js
~~~
var assert = require('power-assert');
describe('Promise Test', function () {
it('should return a promise object', function () {
var promise = Promise.resolve(1);
return promise.then(function (value) {
assert(value === 1);
});
});
});
~~~
這段代碼將前面?[前面使用 `done` 的例子](http://liubin.github.io/promises-book/#promise-assert-fail-fixed)?按照Mocha的Promise測試方式進行了重寫。
修改的地方主要在以下兩點:
* 刪除了?`done`
* 返回結果為promise對象
采用這種寫法的話,當?`assert`?失敗的時候,測試本身自然也會失敗。
~~~
it("should be fail", function () {
return Promise.resolve().then(function () {
assert(false);// => 測試失敗
});
});
~~~
采用這種方法,就能從根本上省略諸如?`.then(done, done);`?這樣本質上跟測試邏輯并無直接關系的代碼。
> [Mocha已經支持對Promises的測試 | Web scratch](http://efcl.info/2014/0314/res3708/)?這篇(日語)文章里也提到了關于Mocha對Promise測試的支持。
## 3.2.1\. 意料之外(失敗的)的測試結果
因為Mocha提供了對Promise的測試,所以我們會認為按照Mocha的規則來寫會比較好。 但是這種代碼可能會帶來意想不到的異常情況的發生。
比如對下面的`mayBeRejected()`?函數的測試代碼,該函數返回一個當滿足某一條件就變為Rejected的promise對象。
想對Error Object進行測試
~~~
function mayBeRejected(){ //這個函數用來對返回的promise對象進行測試
return Promise.reject(new Error("woo"));
}
it("is bad pattern", function () {
return mayBeRejected().catch(function (error) {
assert(error.message === "woo");
});
});
~~~
這個測試的目的包括以下兩點:
`mayBeRejected()`?返回的promise對象如果變為FulFilled狀態的話
測試將會失敗
`mayBeRejected()`?返回的promise對象如果變為Rejected狀態的話
在?`assert`?中對Error對象進行檢查
上面的測試代碼,當promise對象變為Rejected的時候,會調用在?`onRejected`?中注冊的函數,從而沒有走正promise的處理常流程,測試會成功。
這段測試代碼的問題在于當`mayBeRejected()`?返回的是一個?**為FulFilled狀態的promise對象時**,測試會一直成功。
~~~
function mayBeRejected(){ //返回的promise對象會變為FulFilled
return Promise.resolve();
}
it("is bad pattern", function () {
return mayBeRejected().catch(function (error) {
assert(error.message === "woo");
});
});
~~~
在這種情況下,由于在?`catch`?中注冊的?`onRejected`?函數并不會被調用,因此?`assert`?也不會被執行,測試會一直通過(passed,成功)。
為了解決這個問題,我們可以在?`.catch`?的前面加入一個?`.then`?調用,可以理解為如果調用了?`.then`?的話,那么測試就需要失敗。
~~~
function failTest() { //通過throw來使測試失敗
throw new Error("Expected promise to be rejected but it was fulfilled");
}
function mayBeRejected(){
return Promise.resolve();
}
it("should bad pattern", function () {
return mayBeRejected().then(failTest).catch(function (error) {
assert.deepEqual(error.message === "woo");
});
});
~~~
但是,這種寫法會像在前面?[then or catch?](http://liubin.github.io/promises-book/#then-or-catch)?中已經介紹的一樣,?`failTest`?拋出的異常會被?`catch`?捕獲。

Figure 8\. Then Catch flow
程序的執行流程為?`then`?→?`catch`,傳遞給?`catch`?的Error對象為`AssertionError`類型 , 這并不是我們想要的東西。
也就是說,我們希望測試**只能**通過狀態會變為onRejected的promise對象, 如果promise對象狀態為onFulfilled狀態的話,那么該測試就會一直通過。
## 3.2.2\. 明確兩種狀態,改善測試中的意外(異常)狀況
在編寫?[上面對Error對象進行測試的例子](http://liubin.github.io/promises-book/#mocha-rejected-promise-test)?時, 怎么才能剔除那些會意外通過測試的情況呢?
最簡單的方式就是像下面這樣,在測試代碼中判斷在各種promise對象的狀態下,應進行如何的操作。
變為FulFilled狀態的時候
測試會預期失敗
變為Rejected狀態的時候
使用?`assert`?進行測試
也就是說,我們需要在測試代碼中明確指定在Fulfilled和Rejected這兩種狀態下,都需進行什么樣的處理。
~~~
function mayBeRejected() {
return Promise.resolve();
}
it("catch -> then", function () {
// 變為FulFilled的時候測試失敗
return mayBeRejected().then(failTest, function (error) {
assert(error.message === "woo");
});
});
~~~
像這樣的話,就能在promise變為FulFilled的時候編寫出失敗用的測試代碼了。

Figure 9\. Promise onRejected test
在?[then or catch?](http://liubin.github.io/promises-book/#then-or-catch)?中我們已經講過,為了避免遺漏對錯誤的處理, 與使用?`.then(onFulfilled, onRejected)`?這樣帶有二個參數的調用形式相比, 我們更推薦使用?`then`?→?`catch`?這樣的處理方式。
但是在編寫測試代碼的時候,Promise強大的錯誤處理機制反而成了限制我們的障礙。 因此我們不得已采取了?`.then(failTest, onRejected)`?這種寫法,明確指定promise在各種狀態下進行何種的處理。
## 3.2.3\. 總結
在本小節中我們對在使用Mocha進行Promise測試時可能出現的一些意外情況進行了介紹。
* 普通的代碼采用?`then`?→?`catch`?的流程的話比較容易理解
* 這是為了錯誤處理的方便。請參考?[then or catch?](http://liubin.github.io/promises-book/#then-or-catch)
* 將測試代碼集中到?`then`?中處理
* 為了能將AssertionError對象傳遞到測試框架中。
通過使用?`.then(onFulfilled, onRejected)`?這種形式的寫法, 我們可以明確指定promise對象在變為 Fulfilled或Rejected時如何進行處理。
但是,由于需要顯示的指定 Rejected時的測試處理, 像下面這樣的代碼看起來總是有一些讓人感到不太直觀的感覺。
~~~
promise.then(failTest, function(error){
// 使用assert對error進行測試
});
~~~
在下一小節,我們會介紹如何編寫helper函數以方便編寫Promise的測試代碼, 以及怎樣去編寫更容易理解的測試代碼。
- 前言
- 第一章 - 什么是Promise
- 1.1. 什么是Promise
- 1.2. Promise簡介
- 1.3. 編寫Promise代碼
- 第二章 - 實戰Promise
- 2.1. Promise.resolve
- 2.2. Promise.reject
- 2.3. 專欄: Promise只能進行異步操作?
- 2.4. Promise#then
- 2.5. Promise#catch
- 2.6. 專欄: 每次調用then都會返回一個新創建的promise對象
- 2.7. Promise和數組
- 2.8. Promise.all
- 2.9. Promise.race
- 2.10. then or catch?
- 第三章 - Promise測試
- 3.1. 基本測試
- 3.2. Mocha對Promise的支持
- 3.3. 編寫可控測試(controllable tests)
- 第四章 - Advanced
- 4.1. Promise的實現類庫(Library)
- 4.2. Promise.resolve和Thenable
- 4.3. 使用reject而不是throw
- 4.4. Deferred和Promise
- 4.5. 使用Promise.race和delay取消XHR請求
- 4.6. 什么是 Promise.prototype.done ?
- 4.7. Promise和方法鏈(method chain)
- 4.8. 使用Promise進行順序(sequence)處理
- 第五章 - Promises API Reference
- 5.1. Promise#then
- 5.2. Promise#catch
- 5.3. Promise.resolve
- 5.4. Promise.reject
- 5.5. Promise.all
- 5.6. Promise.race
- 第六章 - 用語集
- 第七章 - 參考網站
- 第八章 - 關于作者
- 第九章 - 關于譯者