使用Jasmine的done()进行异步测试?

时间:2015-06-30 16:02:36

标签: javascript typescript jasmine karma-jasmine

我正在尝试使用Jasmine测试一些异步JavaScript(曾经是TypeScript)。我很难让它正常工作,并且通过这个简单的示例,它永远不会进入then(function(代码块,我收到以下错误:

Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

我的测试类似于:

it("Should work", function(done){
    dataService.ready = true;
    dataService.isReady().then(function(result){
        console.log(result);
        expect(result).toBe(true);
        done();
    });
});

我正在测试的服务看起来像(在编译成JavaScript之前):

public isReady(): angular.IPromise<any> {

   var deferred = this.q.defer();

   if (this.ready) {
       setTimeout(() => { return deferred.resolve(true); }, 1);
   } else {
       // a bunch of other stuff that eventually returns a promise
   }

   return deferred.promise;
}

我确信我只是在滥用done(),但我觉得这应该有效!有什么建议吗?

更新

为了进一步调试,我在isReady()函数中添加了一些控制台日志。它现在看起来像:

public isReady(): angular.IPromise<any> {

   var deferred = this.q.defer();
   console.log("in isReady()"); // new line to add logging

   if (this.ready) {
       console.log("this.ready is true"); // new line to add logging
       setTimeout(() => {
            console.log("returning deferred.resolve"); // new line to add logging
            return deferred.resolve(true);
       }, 1);
   } else {
       // a bunch of other stuff that eventually returns a promise
   }

   return deferred.promise;
}
当我在浏览器中手动测试时,

isReady()按预期工作。运行测试时,我的日志包括:

LOG: 'in isReady()'
LOG: 'this.ready is true'
LOG: 'returning deferred.resolve'

在我的测试中,它似乎永远不会得到解决(then()中的代码块永远不会执行)但是在运行我的应用程序时,此功能正常工作。此示例位于控制器中:

DataService.isReady().then(() => {
   console.log("I work!");
});

更新:更多调试......

在我的测试中:

it("Should work", function(done){
    console.log("calling dataService.isReady()");
    var prom = dataService.isReady();

    console.log("promise before");
    console.log(prom);

    setTimeout(function(){
        console.log("promise after");
        console.log(prom);
     },1000);

     prom.then(function(result){
         // never makes it here
         done();
     }, function(reason) {
        // never makes it here either
     });
}

现在,在我的控制台中,我看到了:

LOG: 'calling dataService.isReady()'
LOG: 'in isReady()'
LOG: 'this.ready is true'
LOG: 'promise before'
LOG: Object{$$state: Object{status: 0}}
LOG: 'returning deferred.resolve'
LOG: 'promise after'
LOG: Object{$$state: Object{status: 1, pending: [...], value: true, processScheduled: true}}

所以,我的承诺看起来应该如此。为什么不调用then()

2 个答案:

答案 0 :(得分:1)

实际发生了什么: 所以,事实证明我的问题应该是&#34;为什么我的角度承诺在我的Jasmine测试中没有得到解决?&#34;

$消化

经过一番挖掘并查看其他解决方案后,我找到了一些关于何时/如何解决承诺的好信息。我需要在$digest()上调用$rootScope以解析承诺并执行then()代码块(因此调用done()以满足规范)。

不再需要预期错误

添加$rootScope.$digest()让我大多数的方式,但后来我开始发现导致我的测试失败的No more request expected错误。这是因为我使用的服务正在发送针对我的应用程序的另一方面的各种POSTGET请求。找出whenGETwhenPOST响应似乎解决了这个问题。

最终解决方案:

长话短说,我的spec文件现在看起来像:

describe("Async Tests", function(){

    var dataService;
    var rootScope;
    var httpBackend;

    beforeEach(module("myangularapp"));

    beforeEach(inject(function(_$httpBackend_, _DataService_, $rootScope){

        dataService = _DataService_;
        rootScope = $rootScope;
        httpBackend = _$httpBackend_;

        // solves the 'No more request expected' errors:
        httpBackend.whenGET('').respond([]);
        httpBackend.whenPOST('').respond([]);

    }));

    it("Should work", function(done){

        dataService.ready = true;

        dataService.isReady().then(function(result){

            console.log(result);
            expect(result).toBe(true);

            // still calls done() just as before
            done();

        });

        // digest the scope every so often so we can resolve the promise from the DataService isReady() function
        setInterval(rootScope.$digest, 100);

    });

});

这个解决方案似乎比它需要的更复杂,但我认为它现在可以做到这一点。我希望通过Angular和Jasmine测试异步代码可以帮助其他任何可能遇到挑战的人。

答案 1 :(得分:0)

处理此问题的另一种方法是在测试承诺时始终使用.finally(done),然后再调用$timeout.flush()

"use strict";

describe('Services', function() {
  var $q;
  var $timeout;

  // Include your module
  beforeEach(module('plunker'));

  // When Angular is under test it provides altered names
  // for services so that they don't interfere with
  // outer scope variables like we initialized above.
  // This is nice because it allows for you to use $q
  // in your test instead of having to use _q , $q_ or 
  // some other slightly mangled form of the original
  // service names
  beforeEach(inject(function(_$q_, _$timeout_) {
    $q = _$q_;

    // *** Use angular's timeout, not window.setTimeout() ***
    $timeout = _$timeout_;
  }));

  it('should run DataService.isReady', function(done) {

    // Create a Dataservice
    function  DataService() {}

    // Set up the prototype function isReady
    DataService.prototype.isReady = function () {

      // Keep a reference to this for child scopes
      var _this = this;

      // Create a deferred
      var deferred = $q.defer();

      // If we're ready, start a timeout that will eventually resolve
      if (this.ready) {
        $timeout(function () {

          // *** Note, we're not returning anything so we
          // removed 'return' here. Just calling is needed. ***
          deferred.resolve(true);
        }, 1);
      } else {
        // a bunch of other stuff that eventually returns a promise
        deferred.reject(false);
      }

      // Return the promise now, it will be resolved or rejected in the future
      return deferred.promise;
    };

    // Create an instance
    var ds = new DataService();
    ds.ready = true;

    console.log('got here');

    // Call isReady on this instance
    var prom = ds.isReady();
    console.log(prom.then);

    prom.then(function(result) {
     console.log("I work!");
     expect(result).toBe(true);
    },function(err) {
      console.error(err);
    }, function() {
      console.log('progress?');
    })

    // *** IMPORTANT: done must be called after promise is resolved
    .finally(done);
    $timeout.flush(); // Force digest cycle to resolve promises;
  });
});

http://plnkr.co/edit/LFp214GQcm97Kyv8xnLp

相关问题