IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    JQuery的Promise详解(二):Promise中的单元测试

    dwqs发表于 2015-10-21 08:57:18
    love 0

    续说wait()

    在 Promise基础 的末尾,说到了promise状态更改的问题。

    1. var dtd = $.Deferred(); // 新建一个deferred对象
    2.   var wait = function(dtd){
    3.     var tasks = function(){
    4.       alert("执行完毕!");
    5.       dtd.resolve(); // 改变deferred对象的执行状态
    6.       //dtd.reject(); 改变Deferred对象的执行状态
    7.     };
    8.     setTimeout(tasks,5000);
    9.     return dtd;
    10.   };
    11. //wait()函数运行完,就会自动运行done()方法指定的回调函数。
    12. $.when(wait(dtd))
    13.    .done(function(){ alert("哈哈,成功了!"); })
    14.    .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():

    1. $.Deferred(wait)
    2.   .done(function(){ alert("哈哈,成功了!"); })
    3.   .fail(function(){ alert("出错啦!"); });

    之前有提到,可以通过 dtd.promise() 返回一个Promise对象解决该问题,它的作用是,在原来的deferred对象上返回另一个deferred对象,后者只开放与改变执行状态无关的方法(比如done()方法和fail()方法),屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法),从而使得执行状态不能被改变。利用dtd.promise()还可以直接在wait对象上部署deferred接口。

    1. var dtd = $.Deferred(); // 新建一个deferred对象
    2.   var wait = function(dtd){
    3.     var tasks = function(){
    4.       alert("执行完毕!");
    5.       dtd.resolve(); // 改变deferred对象的执行状态
    6.     };
    7.     setTimeout(tasks,5000);
    8.   };
    9.   dtd.promise(wait);
    10. wait.done(function(){ alert("哈哈,成功了!"); })
    11.    .fail(function(){ alert("出错啦!"); });
    12.  wait(dtd);

    ES6实现了 Promise 接口,可以直接使用:

    1. var wait = function(resolve){
    2.   var tasks = function(){
    3.      alert("执行完毕!");
    4. resolve();
    5.   };
    6.   setTimeout(tasks,5000);
    7. };
    8. var promise = new Promise(function(resolve,reject){
    9. wait(resolve);
    10. });
    11. promise.then(function(){
    12. alert('哈哈,成功了');
    13. },function(){
    14. alert('呜呜,失败了');
    15. });

    Promise的单元测试

    作为Javascript的一部分,Promise被Chrome、Safari等主流浏览器支持。虽说Promise使异步处理变得简单,但是对Promise做单元测试还是比较棘手的问题。接下来就说说怎么处理Promise的单元测试。(完整示例代码)

    Demo 的测试框架是 Mocha,断言库是 Chai。

    一个简单的命令就能安装它们:

    1. npm install mocha chai

    一个典型的单元测试如下:

    1. var expect = require('chai').expect;
    2. it('should do something with promises', function(done) {
    3. //define some data to compare against
    4. var blah = 'foo';
    5. //call the function we're testing
    6. var result = systemUnderTest();
    7. //assertions
    8. result.then(function(data) {
    9. expect(data).to.equal(blah);
    10. done();
    11. }, function(error) {
    12. assert.fail(error);
    13. done();
    14. });
    15. });

    对于promise的断言,添加了两个处理程序:一个用于promise的resolved状态,一个用于promise的rejected状态。但没个处理程序末尾都调用了done()。由于promise是异步的,因而我们的告诉Mocha 这是一个异步单元测试,并通知它什么时候完成。

    使用assert.fail的目的是处理promise失败的状态。如果Promise是失败的,测试也应该是失败的,就应该报一个真实的错误。当然,实际上也可以不用处理这种情况,完全可以移除掉rejection得回调:

    1. result.then(function(data) {
    2. expect(data).to.equal(blah);
    3. done();
    4. });

    Mocha和Promises

    我使用Mocha的原因是Mocha内置是支持promise的,这也意味着promise的reject会导致测试的失败:

    1. it('should fail the test', function() {
    2. var p = Promise.reject('this promise will always be rejected');
    3. return p;
    4. });

    上面的代码会返回一个失败的promise,所以每次运行这个测试都是失败的。可以使用这种方式来改善之前的测试:

    1. var expect = require('chai').expect;
    2. it('should do something with promises', function() {
    3. var blah = 'foo';
    4. var result = systemUnderTest();
    5. return result.then(function(data) {
    6. expect(data).to.equal(blah);
    7. });
    8. });

    改进后的测试会返回一个promise。由于Mocha会处理promise,因而也不需要失败回调或done回调了;如果promise是失败的,测试也是失败的。

    用Chai-as-promised进一步改善测试

    使用 Chai-as-promised 可以直接在promise上使用断言。使用前,还是得安装:

    1. npm install chai-as-promised

    然后,我们的测试代码会是这个样子的:

    1. var chai = require('chai');
    2. var expect = chai.expect;
    3. var chaiAsPromised = require('chai-as-promised');
    4. chai.use(chaiAsPromised);
    5. it('should do something with promises', function() {
    6. var blah = 'foo';
    7. var result = systemUnderTest();
    8. return expect(result).to.eventually.equal(blah);
    9. });

    我们完全用Chai断言代替了then,关键之处是eventually。Chai进行值的比较时,是酱紫的:

    1. expect(value).to.equal(something);

    但在示例中,value 是一个promise,因而插入了eventually,并让其返回:

    1. return expect(value).to.eventually.equal(something)

    现在,Chai就真正能处理promise了。但是要注意:一定要返回promise,不然Mocha不知道要处理promise。

    Promise测试中一些有用的模式

    对象比较

    如果已成功地promise的值是一个对象,可以按照往常正常的方法一样进行比较。例如,使用deep.equal语句:

    1. return expect(value).to.eventually.deep.equal(obj)

    对于对象比较,即使不是promise,equal将比较引用,因而即使对象的属性都相同,也是不同的对象,测试也会失败。

    chai-as-promised提供了一个方便的方法用于对象比较:

    1. return expect(value).to.eventually.become(obj)

    eventually.become 和 deep.equal 是类似的。

    测试对象的一个具体属性

    有时,我们需要核对Promise对象的一个单一属性。可以采取下列方式:

    1. var value = systemUnderTest();
    2. return value.then(function(obj) {
    3. expect(obj.someProp).to.equal('something');
    4. });

    但使用 chai-as-promised是另一种可选择的方式:

    1. var value = systemUnderTest().then(function(obj) {
    2. return obj.someProp;
    3. });
    4. return expect(value).to.eventually.equal('something');

    如果可以使用ES6,是可以简化代码的:

    1. var value = systemUnderTest()
    2. return expect(value.then(o => o.someProp)).to.eventually.equal('something');

    测试多个Promise

    Promise.all 可以用于同时测试多个Promise对象:

    1. return Promise.all([
    2. expect(value1).to.become('foo'),
    3. expect(value2).to.become('bar')
    4. ]);

    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

    如果需要比较多个Promise,则可以采取下面的模式:

    1. return Promise.all([p1, p2]).then(function(values) {
    2. expect(values[0]).to.equal(values[1]);
    3. });

    all 用于所有成功地Promise,then 函数是针对返回值的正常断言。

    失败声明

    如果需要处理Promise失败的情况,可以使用chai-as-promised的rejectWith:

    1. return expect(value).to.be.rejected;

    若要rejection返回一个具体的错误或消息,可以使用rejectedWith:

    1. //require this promise to be rejected with a TypeError
    2. return expect(value).to.be.rejectedWith(TypeError);
    3. //require this promise to be rejected with message 'holy smokes, Batman!'
    4. return expect(value).to.be.rejectedWith('holy smokes, Batman!');

    测试Hooks

    和其它普通的测试函数一样,可以在Promise的测试中使用before, after, beforeEach 和 afterEach:

    1. describe('something', function() {
    2. before(function() {
    3. return somethingThatReturnsAPromise();
    4. });
    5. beforeEach(function() {
    6. return somethingElseWithPromises();
    7. });
    8. });

    Promises 和 Mocks/Stubs

    Promise可以和Stubs一起使用。下面的示例中使用了Sinon.JS,所以要先前安装:

    1. npm install sinon

    从Stubs返回Promise

    如果需要mock或stubs返回Promise,可以这样:

    1. var stub = sinon.stub();
    2. //return a failing promise
    3. stub.returns(Promise.reject('a failure'));
    4. //or a successful promise
    5. stub.returns(Promise.resolve('a success'));

    检测Promises

    可以将spies作为promise的一个回调,但这对于异步的Promise未必有效。可以效仿chai-as-promised的方式对Promise进行断言:

    1. var spy = sinon.spy();
    2. var promise = systemUnderTest();
    3. promise.then(spy);

    Sinon-as-promised

    sinon-as-promised 可以简化Promise和stubs:

    1. npm install sinon-as-promised

    Sinon-as-promised为stubs提供了 resolves 和 rejects 函数:

    1. var sinon = require('sinon');
    2. //this makes sinon-as-promised available in sinon:
    3. require('sinon-as-promised');
    4. var stub = sinon.stub();
    5. //return a failing promise
    6. stub.rejects('a failure');
    7. //or a successful promise
    8. 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总结一:选择器归纳
    无觅

    转载请注明:淡忘~浅思 » JQuery的Promise详解(二):Promise中的单元测试



沪ICP备19023445号-2号
友情链接