嵌套的AngularJS承诺不在单元测试中工作

时间:2014-03-24 03:41:42

标签: javascript angularjs unit-testing jasmine

我正在学习如何使用AngularJS承诺,我在为它们编写单元测试时遇到了问题。我编写了一个带有factory的模块,可以查询RSS提要,解析文章中的标题,并返回一个将解析标题字符串数组的promise。

以下是模块代码:

angular.module('rss', [])

.factory('rssService', ['$http', '$q', '$rootScope', function($http, $q, 
    $rootScope) {
  return function(url) {

    this.getTitles = function() {
      var deferred = $q.defer(),
        titles = [];

      $http({
        method: 'GET',
        url: url
      }).success(function(data) {
        $(data).find('title').each(function(index, item) {
          var title = $(item).text();
          console.log(title);
          titles.push(title);
        });
        deferred.resolve(titles);
      });
      return deferred.promise;
    };
  };
}])
;

这是我的Jasmine单元测试代码:

beforeEach(module('rss'));

describe("RSS Module", function() {

  var rssService, $httpBackend;

  beforeEach(inject(function(_rssService_, _$httpBackend_) {
    rssService = new _rssService_('/rss');
    $httpBackend = _$httpBackend_;
  }));

  it("Can parse an RSS Feed and get the titles", function(done) {
    $httpBackend.expectGET('/rss').respond(mockRSS); // mock data declared earlier
    var titles =  rssService.getTitles();
    $httpBackend.flush();

    titles.then(function(data) {
      console.log("Done!");
      expect(data.length).toBe(17);
      done();
    });


  });
});

当我运行单元测试时,出现以下错误:

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

这是因为,虽然我的承诺得到了解决,但它没有调用$apply这些更改。我读到的解决方案是在解决承诺后注入$rootScope并调用$rootScope.$apply()。这样做会导致以下错误

Error: [$rootScope:inprog] $digest already in progress

这是因为我在$rootScope.$apply()块内调用了success(),所以摘要已经发生了。

在单元测试之外(使用实际的RSS源),代码工作正常。控制器的范围处理摘要,并且按预期工作。

有什么方法吗?

2 个答案:

答案 0 :(得分:4)

您的回答是关于flushthen的顺序。 flush应该在定义回调之后。

此外,当您从$ httpBackend或$ timeout使用flush时,无需使用done:测试变为同步。所以测试可以是:

it("Can parse an RSS Feed and get the titles", function() {
  $httpBackend.expectGET('/rss').respond(mockRSS); // mock data declared earlier
  rssService.getTitles().then(function(data) {
    expect(data.length).toBe(17);
  });
  $httpBackend.flush();
});

此外,我并不认为与您的问题直接相关,但是当您想要对$http的结果进行后期处理时,通常无需手动创建承诺。使用then的{​​{1}}回调代替自定义$http,您可以使用标准承诺链:

success

您可以在http://plnkr.co/edit/efTzOU9RYhVIgLVMdKkd找到此信息。

答案 1 :(得分:0)

我找到的解决方案是移动我的代码行,在代码之后将$httpBackend刷新到以声明then块。发生的事情是GET请求被刷新,我的承诺将在我给它then之前宣布并解决。

新的单元测试代码如下所示:

it("Can parse an RSS Feed and get the titles", function(done) {
  $httpBackend.expectGET('/rss').respond(mockRSS);
  var titles =  rssService.getTitles();

  titles.then(function(data) {
    console.log("Done!");
    expect(data.length).toBe(17);
    done();
  });

  $httpBackend.flush();
});

我没有必要更改任何实际的模块代码,但如果有更好的方法使用链式承诺来编写它,我会很奇怪。非常欢迎评论或其他答案。