在这里,我们将会学习什么是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` 捕获。
![Then Catch flow](https://box.kancloud.cn/2015-07-20_55ac773bdd94f.png)
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的时候编写出失败用的测试代码了。
![Promise onRejected test](https://box.kancloud.cn/2015-07-20_55ac7746a31f4.png)
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
- 第六章 - 用語集
- 第七章 - 参考网站
- 第八章 - 关于作者
- 第九章 - 关于译者