在 Promise基础 的末尾,说到了promise状态更改的问题。
var dtd = $.Deferred(); // 新建一个deferred对象
var wait = function(dtd){
var tasks = function(){
alert("执行完毕!");
dtd.resolve(); // 改变deferred对象的执行状态
//dtd.reject(); 改变Deferred对象的执行状态
};
setTimeout(tasks,5000);
return dtd;
};
//wait()函数运行完,就会自动运行done()方法指定的回调函数。
$.when(wait(dtd))
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
因为dtd是全局对象,会引发在wait()函数外更改dtd状态的问题。
在 Deferred 对象中,还可以接受一个函数名(注意:是函数名)构建deferred对象:
jQuery.Deferred( [beforeStart ] )
beforeStart
Type: Function( Deferred deferred )
A function that is called just before the constructor returns.
保持wait不变,将其传给$.Deferred():
$.Deferred(wait)
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
之前有提到,可以通过 dtd.promise() 返回一个Promise对象解决该问题,它的作用是,在原来的deferred对象上返回另一个deferred对象,后者只开放与改变执行状态无关的方法(比如done()方法和fail()方法),屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法),从而使得执行状态不能被改变。利用dtd.promise()还可以直接在wait对象上部署deferred接口。
var dtd = $.Deferred(); // 新建一个deferred对象
var wait = function(dtd){
var tasks = function(){
alert("执行完毕!");
dtd.resolve(); // 改变deferred对象的执行状态
};
setTimeout(tasks,5000);
};
dtd.promise(wait);
wait.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
wait(dtd);
ES6实现了 Promise 接口,可以直接使用:
var wait = function(resolve){
var tasks = function(){
alert("执行完毕!");
resolve();
};
setTimeout(tasks,5000);
};
var promise = new Promise(function(resolve,reject){
wait(resolve);
});
promise.then(function(){
alert('哈哈,成功了');
},function(){
alert('呜呜,失败了');
});
作为Javascript的一部分,Promise被Chrome、Safari等主流浏览器支持。虽说Promise使异步处理变得简单,但是对Promise做单元测试还是比较棘手的问题。接下来就说说怎么处理Promise的单元测试。(完整示例代码)
一个简单的命令就能安装它们:
npm install mocha chai
一个典型的单元测试如下:
var expect = require('chai').expect;
it('should do something with promises', function(done) {
//define some data to compare against
var blah = 'foo';
//call the function we're testing
var result = systemUnderTest();
//assertions
result.then(function(data) {
expect(data).to.equal(blah);
done();
}, function(error) {
assert.fail(error);
done();
});
});
对于promise的断言,添加了两个处理程序:一个用于promise的resolved状态,一个用于promise的rejected状态。但没个处理程序末尾都调用了done()
。由于promise是异步的,因而我们的告诉Mocha 这是一个异步单元测试,并通知它什么时候完成。
使用assert.fail
的目的是处理promise失败的状态。如果Promise是失败的,测试也应该是失败的,就应该报一个真实的错误。当然,实际上也可以不用处理这种情况,完全可以移除掉rejection得回调:
result.then(function(data) {
expect(data).to.equal(blah);
done();
});
我使用Mocha的原因是Mocha内置是支持promise的,这也意味着promise的reject会导致测试的失败:
it('should fail the test', function() {
var p = Promise.reject('this promise will always be rejected');
return p;
});
上面的代码会返回一个失败的promise,所以每次运行这个测试都是失败的。可以使用这种方式来改善之前的测试:
var expect = require('chai').expect;
it('should do something with promises', function() {
var blah = 'foo';
var result = systemUnderTest();
return result.then(function(data) {
expect(data).to.equal(blah);
});
});
改进后的测试会返回一个promise。由于Mocha会处理promise,因而也不需要失败回调或done
回调了;如果promise是失败的,测试也是失败的。
使用 Chai-as-promised 可以直接在promise上使用断言。使用前,还是得安装:
npm install chai-as-promised
然后,我们的测试代码会是这个样子的:
var chai = require('chai');
var expect = chai.expect;
var chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
it('should do something with promises', function() {
var blah = 'foo';
var result = systemUnderTest();
return expect(result).to.eventually.equal(blah);
});
我们完全用Chai断言代替了then
,关键之处是eventually
。Chai进行值的比较时,是酱紫的:
expect(value).to.equal(something);
但在示例中,value
是一个promise,因而插入了eventually
,并让其返回:
return expect(value).to.eventually.equal(something)
现在,Chai就真正能处理promise了。但是要注意:一定要返回promise,不然Mocha不知道要处理promise。
如果已成功地promise的值是一个对象,可以按照往常正常的方法一样进行比较。例如,使用deep.equal
语句:
return expect(value).to.eventually.deep.equal(obj)
对于对象比较,即使不是promise,equal
将比较引用,因而即使对象的属性都相同,也是不同的对象,测试也会失败。
chai-as-promised提供了一个方便的方法用于对象比较:
return expect(value).to.eventually.become(obj)
eventually.become
和 deep.equal
是类似的。
有时,我们需要核对Promise对象的一个单一属性。可以采取下列方式:
var value = systemUnderTest();
return value.then(function(obj) {
expect(obj.someProp).to.equal('something');
});
但使用 chai-as-promised是另一种可选择的方式:
var value = systemUnderTest().then(function(obj) {
return obj.someProp;
});
return expect(value).to.eventually.equal('something');
如果可以使用ES6,是可以简化代码的:
var value = systemUnderTest()
return expect(value.then(o => o.someProp)).to.eventually.equal('something');
Promise.all
可以用于同时测试多个Promise对象:
return Promise.all([
expect(value1).to.become('foo'),
expect(value2).to.become('bar')
]);
But keep in mind that this is similar to having multiple assertions in a single test, which can be seen as a code smell.
如果需要比较多个Promise,则可以采取下面的模式:
return Promise.all([p1, p2]).then(function(values) {
expect(values[0]).to.equal(values[1]);
});
all
用于所有成功地Promise,then
函数是针对返回值的正常断言。
如果需要处理Promise失败的情况,可以使用chai-as-promised的rejectWith
:
return expect(value).to.be.rejected;
若要rejection返回一个具体的错误或消息,可以使用rejectedWith
:
//require this promise to be rejected with a TypeError
return expect(value).to.be.rejectedWith(TypeError);
//require this promise to be rejected with message 'holy smokes, Batman!'
return expect(value).to.be.rejectedWith('holy smokes, Batman!');
和其它普通的测试函数一样,可以在Promise的测试中使用before
, after
, beforeEach
和 afterEach
:
describe('something', function() {
before(function() {
return somethingThatReturnsAPromise();
});
beforeEach(function() {
return somethingElseWithPromises();
});
});
Promise可以和Stubs一起使用。下面的示例中使用了Sinon.JS,所以要先前安装:
npm install sinon
如果需要mock或stubs返回Promise,可以这样:
var stub = sinon.stub();
//return a failing promise
stub.returns(Promise.reject('a failure'));
//or a successful promise
stub.returns(Promise.resolve('a success'));
可以将spies作为promise的一个回调,但这对于异步的Promise未必有效。可以效仿chai-as-promised的方式对Promise进行断言:
var spy = sinon.spy();
var promise = systemUnderTest();
promise.then(spy);
sinon-as-promised 可以简化Promise和stubs:
npm install sinon-as-promised
Sinon-as-promised为stubs提供了 resolves
和 rejects
函数:
var sinon = require('sinon');
//this makes sinon-as-promised available in sinon:
require('sinon-as-promised');
var stub = sinon.stub();
//return a failing promise
stub.rejects('a failure');
//or a successful promise
stub.resolves('a success');
Promise简化了异步代码,Mocha内置了对Promise的支持,结合Chai、chai-as-promised,使得Promise易于被测试。但必须记住的是:在测试中使用promise时,必须从test中返回promise,否则Mocha是不会处理Promise的。
参考文章:
Promises in JavaScript Unit Tests: the Definitive Guide
淡忘~浅思猜你喜欢 | ||||
JQuery的Promise详解(一):Promise基础 |
JQuery总结三:DOM完全操作和动画 |
DOM笔记(六):怎么进行JQuery扩展? |
jQuery中实现自定义方法的扩展 |
JQuery总结一:选择器归纳 |
无觅 |