牛骨文教育服务平台(让学习变的简单)

在这里,我们将会学习什么是Mocha支持的“对Promise测试”。

官方网站 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 的例子 按照Mocha的Promise测试方式进行了重写。

修改的地方主要在以下两点:

  • 删除了 done

  • 返回结果为promise对象

采用这种写法的话,当 assert 失败的时候,测试本身自然也会失败。

it("should be fail", function () {
    return Promise.resolve().then(function () {
        assert(false);// => 测试失败
    });
});

采用这种方法,就能从根本上省略诸如 .then(done, done); 这样本质上跟测试逻辑并无直接关系的代码。

Mocha已经支持对Promises的测试 | Web scratch 这篇(日语)文章里也提到了关于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? 中已经介绍的一样, failTest 抛出的异常会被 catch 捕获。

Then Catch flow

Figure 8. Then Catch flow

程序的执行流程为 then → catch,传递给 catch 的Error对象为AssertionError类型 , 这并不是我们想要的东西。

也就是说,我们希望测试只能通过状态会变为onRejected的promise对象, 如果promise对象状态为onFulfilled状态的话,那么该测试就会一直通过。

3.2.2. 明确两种状态,改善测试中的意外(异常)状况

在编写 上面对Error对象进行测试的例子 时, 怎么才能剔除那些会意外通过测试的情况呢?

最简单的方式就是像下面这样,在测试代码中判断在各种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

Figure 9. Promise onRejected test

在 then or catch? 中我们已经讲过,为了避免遗漏对错误的处理, 与使用 .then(onFulfilled, onRejected) 这样带有二个参数的调用形式相比, 我们更推荐使用 then → catch 这样的处理方式。

但是在编写测试代码的时候,Promise强大的错误处理机制反而成了限制我们的障碍。 因此我们不得已采取了 .then(failTest, onRejected) 这种写法,明确指定promise在各种状态下进行何种的处理。

3.2.3. 总结

在本小节中我们对在使用Mocha进行Promise测试时可能出现的一些意外情况进行了介绍。

  • 普通的代码采用 then → catch 的流程的话比较容易理解

  • 将测试代码集中到 then 中处理

*   为了能将AssertionError对象传递到测试框架中。


通过使用 .then(onFulfilled, onRejected) 这种形式的写法, 我们可以明确指定promise对象在变为 Fulfilled或Rejected时如何进行处理。

但是,由于需要显示的指定 Rejected时的测试处理, 像下面这样的代码看起来总是有一些让人感到不太直观的感觉。

promise.then(failTest, function(error){
    // 使用assert对error进行测试
});

在下一小节,我们会介绍如何编写helper函数以方便编写Promise的测试代码, 以及怎样去编写更容易理解的测试代码。